[med-svn] [phyutility] 01/02: Imported Upstream version 2.6
Stephen Smith
blackrim-guest at moszumanska.debian.org
Tue Mar 4 18:14:39 UTC 2014
This is an automated email from the git hooks/post-receive script.
blackrim-guest pushed a commit to branch master
in repository phyutility.
commit 2e6a5cb5b0614907f54778eb9f57e29b5bc5c0b2
Author: blackrim-guest <blackrim at gmail.com>
Date: Mon Mar 3 21:09:41 2014 -0500
Imported Upstream version 2.6
---
build.xml | 20 +
examples/test.aln | 155 ++
examples/test.con | 16 +
examples/test.fasta | 155 ++
examples/test.gb | 44 +
examples/test.nex | 14 +
examples/test.tre | 1 +
examples/test2.aln | 124 ++
examples/test_50.nex | 23 +
examples/test_all.aln | 14 +
examples/test_all.tre | 5 +
examples/test_gb1.fasta | 12 +
examples/test_lm.tre | 5 +
examples/test_pr.tre | 1 +
examples/test_rr.tre | 1 +
examples/test_ts.tre | 5 +
examples/test_vert.nex | 16 +
gpl.txt | 674 +++++++
manual.pdf | Bin 0 -> 410830 bytes
phyutility | 1 +
phyutility.jar | Bin 0 -> 1029518 bytes
phyutility_sm.png | Bin 0 -> 7998 bytes
src/jade/data/AbstractAlignment.java | 207 +++
src/jade/data/Alignment.java | 158 ++
src/jade/data/AlignmentReader.java | 483 +++++
src/jade/data/BinaryDataType.java | 131 ++
src/jade/data/ConcatenateAlignment.java | 148 ++
src/jade/data/DataType.java | 58 +
src/jade/data/FastaReader.java | 60 +
src/jade/data/GBParser.java | 283 +++
src/jade/data/NucleotideDataType.java | 297 +++
src/jade/data/NumericDataType.java | 19 +
src/jade/data/Sequence.java | 15 +
src/jade/data/SitePattern.java | 19 +
src/jade/data/TransitionPenaltyTable.java | 24 +
src/jade/lib/compile.sh | 3 +
src/jade/lib/instr | 1 +
src/jade/lib/jade_reconstruct_area_PJNI.c | 35 +
src/jade/lib/jade_reconstruct_area_PJNI.h | 21 +
src/jade/lib/libmatrixExp.so | Bin 0 -> 2600 bytes
src/jade/math/AP_Basic.java | 421 +++++
src/jade/math/AP_Praxis.java | 420 +++++
src/jade/math/AP_praxis_method.java | 6 +
src/jade/math/BetaDistribution.java | 51 +
src/jade/math/ContinuedFraction.java | 58 +
src/jade/math/ExponentialDistribution.java | 48 +
src/jade/math/GammaDistribution.java | 102 +
src/jade/math/GammaFunction.java | 65 +
src/jade/math/IncompleteGammaFunction.java | 56 +
src/jade/math/IncompleteGammaFunctionFraction.java | 36 +
src/jade/math/IncompleteGammaFunctionSeries.java | 36 +
src/jade/math/InfiniteSeries.java | 42 +
src/jade/math/IterativeProcess.java | 61 +
src/jade/math/MachinePrecision.java | 141 ++
src/jade/math/NChooseM.java | 195 ++
src/jade/math/NormalDistribution.java | 44 +
src/jade/math/PrecisionCalculator.java | 57 +
src/jade/math/ProbDistribution.java | 27 +
src/jade/math/Utils.java | 393 ++++
src/jade/math/fRan.java | 57 +
src/jade/reconstruct/area/AncSplit.java | 20 +
src/jade/reconstruct/area/Area.java | 6 +
src/jade/reconstruct/area/BayesChain.java | 22 +
.../area/BayesianMarginalRangeReconstructor.java | 272 +++
.../BayesianMarginalRangeReconstructorMult.java | 237 +++
src/jade/reconstruct/area/BranchSegment.java | 54 +
.../reconstruct/area/JointRangeReconstructor.java | 243 +++
.../area/MarginalRangeReconstructor.java | 526 ++++++
src/jade/reconstruct/area/MatrixExponential.java | 801 ++++++++
src/jade/reconstruct/area/P.java | 35 +
src/jade/reconstruct/area/PJAMA.java | 100 +
src/jade/reconstruct/area/PJNI.java | 8 +
src/jade/reconstruct/area/Q.java | 93 +
.../area/RangeReconstructionOptimizer.java | 67 +
.../RangeReconstructionOptimizerDispersal.java | 107 ++
...angeReconstructionOptimizerLocalExtinction.java | 71 +
.../area/RangeReconstructionOptimizerMult.java | 70 +
.../RangeReconstructionOptimizerMultDispersal.java | 91 +
...angeReconstructionOptimizerMultDispersalLB.java | 123 ++
src/jade/reconstruct/area/RangeReconstructor.java | 11 +
src/jade/reconstruct/area/RateModel.java | 271 +++
.../discrete/MultiStateMarginalCalculator.java | 201 ++
src/jade/reconstruct/discrete/P.java | 75 +
src/jade/runners/ConcatMain.java | 31 +
src/jade/runners/GBParserMain.java | 60 +
src/jade/simulate/Area.java | 133 ++
src/jade/simulate/BioGeoSim.java | 13 +
src/jade/simulate/BioGeoTree.java | 564 ++++++
src/jade/simulate/BioGeoTree2.java | 614 ++++++
src/jade/simulate/BirthDeathTree.java | 157 ++
src/jade/simulate/Connection.java | 18 +
src/jade/simulate/ConstantEpisode.java | 12 +
src/jade/simulate/EpisodicTree.java | 368 ++++
src/jade/simulate/ExtinctionEpisode.java | 8 +
src/jade/simulate/ExtinctionFunction.java | 29 +
src/jade/simulate/GrowthEpisode.java | 12 +
src/jade/simulate/Lineage.java | 101 +
src/jade/tree/Node.java | 188 ++
src/jade/tree/NodeObject.java | 16 +
src/jade/tree/Tree.java | 382 ++++
src/jade/tree/TreeFileReader.java | 5 +
src/jade/tree/TreeObject.java | 16 +
src/jade/tree/TreePrinter.java | 333 ++++
src/jade/tree/TreeReader.java | 158 ++
src/jade/tree/TreeSimulator.java | 8 +
src/jade/tree/TreeUtils.java | 67 +
src/jebl/evolution/align/Align.java | 253 +++
src/jebl/evolution/align/AlignAffine.java | 117 ++
src/jebl/evolution/align/AlignCommand.java | 453 +++++
src/jebl/evolution/align/AlignLinearSpace.java | 45 +
.../evolution/align/AlignLinearSpaceAffine.java | 48 +
src/jebl/evolution/align/AlignRepeat.java | 89 +
src/jebl/evolution/align/AlignRepeatAffine.java | 104 ++
src/jebl/evolution/align/AlignSimple.java | 90 +
src/jebl/evolution/align/AlignmentResult.java | 41 +
.../align/AlignmentTreeBuilderFactory.java | 179 ++
src/jebl/evolution/align/BartonSternberg.java | 364 ++++
.../align/CompoundAlignmentProgressListener.java | 58 +
src/jebl/evolution/align/MaximalSegmentPair.java | 56 +
src/jebl/evolution/align/MultipleAligner.java | 21 +
src/jebl/evolution/align/NeedlemanWunsch.java | 83 +
.../evolution/align/NeedlemanWunschAffine.java | 329 ++++
.../align/NeedlemanWunschLinearSpace.java | 131 ++
.../align/NeedlemanWunschLinearSpaceAffine.java | 701 +++++++
.../align/NonOverlapMultipleLocalAffine.java | 191 ++
.../evolution/align/OldNeedlemanWunschAffine.java | 108 ++
src/jebl/evolution/align/Output.java | 8 +
src/jebl/evolution/align/OverlapAlign.java | 56 +
src/jebl/evolution/align/PairwiseAligner.java | 27 +
src/jebl/evolution/align/Profile.java | 331 ++++
src/jebl/evolution/align/ProfileCharacter.java | 168 ++
.../align/SequenceAlignmentsDistanceMatrix.java | 61 +
src/jebl/evolution/align/SequenceShuffler.java | 112 ++
src/jebl/evolution/align/SmithWaterman.java | 56 +
.../evolution/align/SmithWatermanLinearSpace.java | 97 +
.../align/SmithWatermanLinearSpaceAffine.java | 121 ++
src/jebl/evolution/align/SystemOut.java | 8 +
src/jebl/evolution/align/Traceback.java | 12 +
src/jebl/evolution/align/TracebackAffine.java | 19 +
src/jebl/evolution/align/TracebackPlotter.java | 17 +
src/jebl/evolution/align/TracebackSimple.java | 13 +
src/jebl/evolution/align/package.html | 16 +
.../evolution/align/scores/AminoAcidScores.java | 25 +
src/jebl/evolution/align/scores/Blosum45.java | 32 +
src/jebl/evolution/align/scores/Blosum50.java | 35 +
src/jebl/evolution/align/scores/Blosum55.java | 30 +
src/jebl/evolution/align/scores/Blosum60.java | 30 +
src/jebl/evolution/align/scores/Blosum62.java | 30 +
src/jebl/evolution/align/scores/Blosum65.java | 30 +
src/jebl/evolution/align/scores/Blosum70.java | 30 +
src/jebl/evolution/align/scores/Blosum75.java | 30 +
src/jebl/evolution/align/scores/Blosum80.java | 30 +
src/jebl/evolution/align/scores/Blosum85.java | 30 +
src/jebl/evolution/align/scores/Blosum90.java | 30 +
src/jebl/evolution/align/scores/Hamming.java | 26 +
src/jebl/evolution/align/scores/JukesCantor.java | 29 +
.../evolution/align/scores/NucleotideScores.java | 159 ++
src/jebl/evolution/align/scores/Pam100.java | 30 +
src/jebl/evolution/align/scores/Pam110.java | 30 +
src/jebl/evolution/align/scores/Pam120.java | 30 +
src/jebl/evolution/align/scores/Pam130.java | 30 +
src/jebl/evolution/align/scores/Pam140.java | 30 +
src/jebl/evolution/align/scores/Pam150.java | 30 +
src/jebl/evolution/align/scores/Pam160.java | 30 +
src/jebl/evolution/align/scores/Pam170.java | 30 +
src/jebl/evolution/align/scores/Pam180.java | 30 +
src/jebl/evolution/align/scores/Pam190.java | 30 +
src/jebl/evolution/align/scores/Pam200.java | 30 +
src/jebl/evolution/align/scores/Pam210.java | 30 +
src/jebl/evolution/align/scores/Pam220.java | 30 +
src/jebl/evolution/align/scores/Pam230.java | 30 +
src/jebl/evolution/align/scores/Pam240.java | 30 +
src/jebl/evolution/align/scores/Pam250.java | 30 +
src/jebl/evolution/align/scores/ScoreMatrix.java | 24 +
src/jebl/evolution/align/scores/Scores.java | 207 +++
src/jebl/evolution/align/scores/ScoresFactory.java | 109 ++
.../evolution/align/scores/SubstScoreMatrix.java | 48 +
src/jebl/evolution/aligners/Aligner.java | 26 +
src/jebl/evolution/alignments/Alignment.java | 27 +
src/jebl/evolution/alignments/BasicAlignment.java | 241 +++
.../alignments/BootstrappedAlignment.java | 25 +
.../evolution/alignments/ConsensusSequence.java | 151 ++
.../evolution/alignments/JackknifedAlignment.java | 29 +
src/jebl/evolution/alignments/Pattern.java | 76 +
src/jebl/evolution/alignments/Patterns.java | 46 +
.../evolution/alignments/ResampledAlignment.java | 97 +
.../MLContinuousCharacterReconstructor.java | 245 +++
src/jebl/evolution/characters/Character.java | 60 +
src/jebl/evolution/characters/CharacterType.java | 17 +
.../evolution/characters/ContinuousCharacter.java | 89 +
.../evolution/characters/DiscreteCharacter.java | 125 ++
.../coalescent/CataclysmicDemographic.java | 132 ++
src/jebl/evolution/coalescent/Coalescent.java | 155 ++
.../evolution/coalescent/ConstExponential.java | 149 ++
src/jebl/evolution/coalescent/ConstLogistic.java | 123 ++
.../evolution/coalescent/ConstantPopulation.java | 104 ++
.../evolution/coalescent/DemographicFunction.java | 114 ++
src/jebl/evolution/coalescent/Expansion.java | 125 ++
.../evolution/coalescent/ExponentialGrowth.java | 137 ++
src/jebl/evolution/coalescent/IntervalList.java | 127 ++
src/jebl/evolution/coalescent/Intervals.java | 228 +++
src/jebl/evolution/coalescent/LogisticGrowth.java | 133 ++
.../evolution/distances/BasicDistanceMatrix.java | 124 ++
.../CannotBuildDistanceMatrixException.java | 11 +
src/jebl/evolution/distances/DistanceMatrix.java | 55 +
.../evolution/distances/F84DistanceMatrix.java | 93 +
.../evolution/distances/HKYDistanceMatrix.java | 209 +++
.../distances/JukesCantorDistanceMatrix.java | 111 ++
.../distances/ModelBasedDistanceMatrix.java | 130 ++
.../distances/TamuraNeiDistanceMatrix.java | 172 ++
src/jebl/evolution/graphs/Edge.java | 18 +
src/jebl/evolution/graphs/Graph.java | 97 +
src/jebl/evolution/graphs/Node.java | 26 +
src/jebl/evolution/graphs/Utils.java | 45 +
src/jebl/evolution/io/AlignmentExporter.java | 21 +
src/jebl/evolution/io/AlignmentImporter.java | 20 +
src/jebl/evolution/io/ByteBuilder.java | 67 +
src/jebl/evolution/io/DistanceMatrixImporter.java | 21 +
src/jebl/evolution/io/FastaExporter.java | 46 +
src/jebl/evolution/io/FastaImporter.java | 254 +++
src/jebl/evolution/io/IllegalCharacterPolicy.java | 33 +
.../evolution/io/ImmediateSequenceImporter.java | 23 +
src/jebl/evolution/io/ImportException.java | 58 +
src/jebl/evolution/io/ImportHelper.java | 746 ++++++++
src/jebl/evolution/io/MEGAExporter.java | 66 +
src/jebl/evolution/io/NewickExporter.java | 56 +
src/jebl/evolution/io/NewickImporter.java | 220 +++
src/jebl/evolution/io/NexusExporter.java | 386 ++++
src/jebl/evolution/io/NexusImporter.java | 1383 ++++++++++++++
src/jebl/evolution/io/PHYLIPExporter.java | 117 ++
.../evolution/io/PhylipSequentialImporter.java | 79 +
src/jebl/evolution/io/SequenceExporter.java | 21 +
src/jebl/evolution/io/SequenceImporter.java | 31 +
src/jebl/evolution/io/TabDelimitedImporter.java | 164 ++
src/jebl/evolution/io/TreeExporter.java | 30 +
src/jebl/evolution/io/TreeImporter.java | 55 +
src/jebl/evolution/parsimony/FitchParsimony.java | 338 ++++
.../evolution/parsimony/ParsimonyCriterion.java | 51 +
src/jebl/evolution/sequences/AminoAcidState.java | 53 +
src/jebl/evolution/sequences/AminoAcids.java | 204 ++
src/jebl/evolution/sequences/BasicSequence.java | 218 +++
.../evolution/sequences/CanonicalSequence.java | 194 ++
src/jebl/evolution/sequences/CodonState.java | 30 +
src/jebl/evolution/sequences/Codons.java | 134 ++
src/jebl/evolution/sequences/FilteredSequence.java | 134 ++
src/jebl/evolution/sequences/GaplessSequence.java | 18 +
src/jebl/evolution/sequences/GeneticCode.java | 298 +++
src/jebl/evolution/sequences/NucleotideState.java | 49 +
src/jebl/evolution/sequences/Nucleotides.java | 184 ++
src/jebl/evolution/sequences/Sequence.java | 59 +
.../sequences/SequenceStateException.java | 21 +
src/jebl/evolution/sequences/SequenceTester.java | 63 +
src/jebl/evolution/sequences/SequenceType.java | 355 ++++
src/jebl/evolution/sequences/Sequences.java | 28 +
src/jebl/evolution/sequences/State.java | 92 +
.../evolution/sequences/StateClassification.java | 58 +
.../evolution/sequences/TranslatedSequence.java | 29 +
src/jebl/evolution/sequences/Utils.java | 346 ++++
.../evolution/substmodel/AbstractRateMatrix.java | 297 +++
src/jebl/evolution/substmodel/AminoAcidModel.java | 36 +
.../evolution/substmodel/MatrixExponential.java | 912 +++++++++
src/jebl/evolution/substmodel/RateMatrix.java | 74 +
src/jebl/evolution/substmodel/WAG.java | 202 ++
src/jebl/evolution/taxa/MissingTaxonException.java | 11 +
src/jebl/evolution/taxa/Taxon.java | 168 ++
src/jebl/evolution/taxa/TaxonomicLevel.java | 77 +
src/jebl/evolution/treemetrics/BilleraMetric.java | 26 +
.../evolution/treemetrics/CladeHeightMetric.java | 218 +++
.../treemetrics/RobinsonsFouldMetric.java | 91 +
.../evolution/treemetrics/RootedTreeMetric.java | 18 +
.../evolution/trees/AttributedCladeSystem.java | 105 ++
src/jebl/evolution/trees/BaseEdge.java | 59 +
src/jebl/evolution/trees/BaseNode.java | 58 +
src/jebl/evolution/trees/CalculateSplitRates.java | 624 +++++++
src/jebl/evolution/trees/CladeSystem.java | 143 ++
.../evolution/trees/ClusteringTreeBuilder.java | 240 +++
src/jebl/evolution/trees/CompactRootedTree.java | 576 ++++++
src/jebl/evolution/trees/ConsensusTreeBuilder.java | 128 ++
src/jebl/evolution/trees/FilteredRootedTree.java | 155 ++
.../trees/GreedyRootedConsensusTreeBuilder.java | 298 +++
.../trees/GreedyUnrootedConsensusTreeBuilder.java | 416 +++++
src/jebl/evolution/trees/HashPair.java | 30 +
.../evolution/trees/MRCACConsensusTreeBuilder.java | 301 +++
src/jebl/evolution/trees/MostProbableTopology.java | 472 +++++
src/jebl/evolution/trees/MutableRootedTree.java | 795 ++++++++
.../trees/NeighborJoiningTreeBuilder.java | 137 ++
src/jebl/evolution/trees/RootedFromUnrooted.java | 320 ++++
src/jebl/evolution/trees/RootedTree.java | 95 +
src/jebl/evolution/trees/RootedTreeUtils.java | 308 ++++
src/jebl/evolution/trees/SimpleRootedTree.java | 741 ++++++++
src/jebl/evolution/trees/SimpleTree.java | 450 +++++
src/jebl/evolution/trees/SortedRootedTree.java | 75 +
src/jebl/evolution/trees/SplitSystem.java | 118 ++
src/jebl/evolution/trees/SplitUtils.java | 140 ++
.../evolution/trees/TransformedRootedTree.java | 110 ++
src/jebl/evolution/trees/Tree.java | 84 +
src/jebl/evolution/trees/TreeBiPartitionInfo.java | 131 ++
src/jebl/evolution/trees/TreeBuilder.java | 17 +
src/jebl/evolution/trees/TreeBuilderFactory.java | 130 ++
src/jebl/evolution/trees/UPGMATreeBuilder.java | 71 +
src/jebl/evolution/trees/Utils.java | 680 +++++++
.../CoalescentIntervalGenerator.java | 208 +++
.../treesimulation/IntervalGenerator.java | 18 +
.../evolution/treesimulation/TreeSimulator.java | 238 +++
.../trees/treecomponent/RootedTreeComponent.java | 192 ++
.../gui/trees/treecomponent/RootedTreePainter.java | 54 +
.../gui/trees/treecomponent/SquareTreePainter.java | 311 ++++
.../gui/trees/treeviewer/MultipleTreeViewer.java | 132 ++
.../gui/trees/treeviewer/TreeDrawableElement.java | 464 +++++
.../trees/treeviewer/TreeDrawableElementLabel.java | 95 +
.../treeviewer/TreeDrawableElementNodeLabel.java | 132 ++
src/jebl/gui/trees/treeviewer/TreePane.java | 1947 ++++++++++++++++++++
src/jebl/gui/trees/treeviewer/TreePaneRuler.java | 56 +
.../gui/trees/treeviewer/TreePaneSelector.java | 194 ++
.../trees/treeviewer/TreeSelectionListener.java | 10 +
src/jebl/gui/trees/treeviewer/TreeViewer.java | 720 ++++++++
.../decorators/AttributeBranchDecorator.java | 36 +
.../treeviewer/decorators/BranchDecorator.java | 14 +
.../treeviewer/decorators/TaxonDecorator.java | 14 +
src/jebl/gui/trees/treeviewer/images/polarTree.png | Bin 0 -> 417 bytes
.../gui/trees/treeviewer/images/radialTree.png | Bin 0 -> 351 bytes
.../trees/treeviewer/images/rectangularTree.png | Bin 0 -> 431 bytes
.../trees/treeviewer/painters/AbstractPainter.java | 25 +
.../treeviewer/painters/BasicLabelPainter.java | 502 +++++
.../gui/trees/treeviewer/painters/Painter.java | 46 +
.../trees/treeviewer/painters/PainterListener.java | 11 +
.../trees/treeviewer/painters/ScaleBarPainter.java | 338 ++++
.../treeviewer/treelayouts/AbstractTreeLayout.java | 93 +
.../treeviewer/treelayouts/PolarTreeLayout.java | 356 ++++
.../treeviewer/treelayouts/RadialTreeLayout.java | 366 ++++
.../treelayouts/RectilinearTreeLayout.java | 373 ++++
.../trees/treeviewer/treelayouts/TreeLayout.java | 135 ++
.../treeviewer/treelayouts/TreeLayoutListener.java | 11 +
.../trees/treeviewer_dev/DefaultTreeViewer.java | 472 +++++
.../trees/treeviewer_dev/MultiPaneTreeViewer.java | 561 ++++++
.../MultiPaneTreeViewerController.java | 94 +
.../treeviewer_dev/MultipleTreesController.java | 88 +
.../treeviewer_dev/TreeAppearanceController.java | 232 +++
src/jebl/gui/trees/treeviewer_dev/TreePane.java | 1306 +++++++++++++
.../gui/trees/treeviewer_dev/TreePaneListener.java | 10 +
.../gui/trees/treeviewer_dev/TreePaneRuler.java | 56 +
.../gui/trees/treeviewer_dev/TreePaneSelector.java | 189 ++
.../treeviewer_dev/TreeSelectionListener.java | 10 +
src/jebl/gui/trees/treeviewer_dev/TreeViewer.java | 138 ++
.../trees/treeviewer_dev/TreeViewerController.java | 348 ++++
.../trees/treeviewer_dev/TreeViewerListener.java | 11 +
.../gui/trees/treeviewer_dev/TreeViewerPanel.java | 145 ++
.../gui/trees/treeviewer_dev/TreesController.java | 157 ++
.../decorators/AttributableDecorator.java | 120 ++
.../decorators/ContinuousColorDecorator.java | 144 ++
.../trees/treeviewer_dev/decorators/Decorator.java | 19 +
.../decorators/DiscreteColorDecorator.java | 165 ++
.../gui/trees/treeviewer_dev/images/polarTree.png | Bin 0 -> 417 bytes
.../gui/trees/treeviewer_dev/images/radialTree.png | Bin 0 -> 351 bytes
.../treeviewer_dev/images/rectangularTree.png | Bin 0 -> 431 bytes
.../treeviewer_dev/painters/AbstractPainter.java | 32 +
.../treeviewer_dev/painters/BasicLabelPainter.java | 306 +++
.../treeviewer_dev/painters/LabelPainter.java | 102 +
.../painters/LabelPainterController.java | 213 +++
.../treeviewer_dev/painters/NodeBarController.java | 136 ++
.../treeviewer_dev/painters/NodeBarPainter.java | 202 ++
.../painters/NodeHistController.java | 136 ++
.../treeviewer_dev/painters/NodeHistPainter.java | 202 ++
.../trees/treeviewer_dev/painters/NodePainter.java | 79 +
.../painters/NodeShapeController.java | 166 ++
.../treeviewer_dev/painters/NodeShapePainter.java | 137 ++
.../gui/trees/treeviewer_dev/painters/Painter.java | 69 +
.../treeviewer_dev/painters/PainterListener.java | 13 +
.../treeviewer_dev/painters/ScaleAxisPainter.java | 313 ++++
.../treeviewer_dev/painters/ScaleBarAxis.java | 652 +++++++
.../treeviewer_dev/painters/ScaleBarPainter.java | 221 +++
.../painters/ScaleBarPainterController.java | 219 +++
.../treelayouts/AbstractTreeLayout.java | 71 +
.../treelayouts/PolarTreeLayout.java | 519 ++++++
.../treelayouts/PolarTreeLayoutController.java | 157 ++
.../treelayouts/RadialTreeLayout.java | 187 ++
.../treelayouts/RadialTreeLayoutController.java | 83 +
.../treelayouts/RectilinearTreeLayout.java | 455 +++++
.../RectilinearTreeLayoutController.java | 107 ++
.../treeviewer_dev/treelayouts/TreeLayout.java | 101 +
.../treelayouts/TreeLayoutCache.java | 94 +
.../treelayouts/TreeLayoutListener.java | 11 +
src/jebl/math/Binomial.java | 79 +
src/jebl/math/GammaFunction.java | 204 ++
src/jebl/math/MachineAccuracy.java | 87 +
src/jebl/math/MatrixCalc.java | 320 ++++
src/jebl/math/MatrixCalcException.java | 16 +
src/jebl/math/MersenneTwisterFast.java | 779 ++++++++
src/jebl/math/MinimiserMonitor.java | 146 ++
src/jebl/math/MultivariateFunction.java | 65 +
src/jebl/math/MultivariateMinimum.java | 259 +++
src/jebl/math/NumericalDerivative.java | 140 ++
src/jebl/math/OrderEnumerator.java | 443 +++++
src/jebl/math/OrthogonalHints.java | 201 ++
src/jebl/math/OrthogonalLineFunction.java | 122 ++
src/jebl/math/OrthogonalSearch.java | 318 ++++
src/jebl/math/Random.java | 193 ++
src/jebl/math/UnivariateFunction.java | 45 +
src/jebl/math/UnivariateMinimum.java | 509 +++++
src/jebl/util/Attributable.java | 68 +
src/jebl/util/AttributableHelper.java | 37 +
src/jebl/util/CompositeProgressListener.java | 197 ++
src/jebl/util/FixedBitSet.java | 227 +++
src/jebl/util/NumberFormatter.java | 136 ++
src/jebl/util/ProgressListener.java | 165 ++
src/jebl/util/Utils.java | 455 +++++
src/org/virion/jam/app/Arguments.java | 514 ++++++
src/org/virion/jam/app/Utils.java | 137 ++
src/org/virion/jam/components/JVerticalLabel.java | 99 +
src/org/virion/jam/components/RealNumberField.java | 215 +++
.../virion/jam/components/WholeNumberField.java | 191 ++
src/org/virion/jam/console/ConsoleApplication.java | 82 +
src/org/virion/jam/console/ConsoleFrame.java | 157 ++
.../virion/jam/console/ConsoleMenuBarFactory.java | 30 +
.../jam/controlpalettes/AbstractController.java | 39 +
.../jam/controlpalettes/BasicControlPalette.java | 249 +++
.../virion/jam/controlpalettes/ControlPalette.java | 65 +
.../controlpalettes/ControlPaletteListener.java | 10 +
src/org/virion/jam/controlpalettes/Controller.java | 66 +
.../jam/controlpalettes/ControllerListener.java | 10 +
.../virion/jam/controlpalettes/PinnedButton.java | 65 +
.../virion/jam/controlpalettes/images/pinIn.png | Bin 0 -> 501 bytes
.../jam/controlpalettes/images/pinInRollover.png | Bin 0 -> 592 bytes
.../virion/jam/controlpalettes/images/pinOut.png | Bin 0 -> 521 bytes
.../jam/controlpalettes/images/pinOutRollover.png | Bin 0 -> 618 bytes
.../jam/controlpanels/BasicControlPalette.java | 167 ++
.../virion/jam/controlpanels/ControlPalette.java | 25 +
.../jam/controlpanels/ControlPaletteListener.java | 10 +
src/org/virion/jam/controlpanels/Controls.java | 65 +
.../virion/jam/controlpanels/ControlsProvider.java | 40 +
.../virion/jam/controlpanels/ControlsSettings.java | 12 +
src/org/virion/jam/demo/DemoApplication.java | 137 ++
src/org/virion/jam/demo/DemoFrame.java | 168 ++
src/org/virion/jam/demo/DemoMenuBarFactory.java | 25 +
.../virion/jam/demo/images/filterBackground.png | Bin 0 -> 180 bytes
src/org/virion/jam/demo/images/find.png | Bin 0 -> 712 bytes
src/org/virion/jam/demo/images/infoTool.png | Bin 0 -> 1919 bytes
src/org/virion/jam/demo/images/prefsAdvanced.png | Bin 0 -> 3135 bytes
src/org/virion/jam/demo/images/prefsClipboard.png | Bin 0 -> 1839 bytes
src/org/virion/jam/demo/images/prefsGeneral.png | Bin 0 -> 1349 bytes
src/org/virion/jam/demo/images/projectTool.png | Bin 0 -> 3135 bytes
src/org/virion/jam/demo/images/tableSelection.png | Bin 0 -> 335 bytes
.../virion/jam/demo/images/tableSelectionGray.png | Bin 0 -> 340 bytes
src/org/virion/jam/demo/menus/DemoMenuFactory.java | 49 +
src/org/virion/jam/demo/menus/DemoMenuHandler.java | 14 +
.../virion/jam/disclosure/DisclosureButton.java | 173 ++
.../virion/jam/disclosure/DisclosureListener.java | 16 +
src/org/virion/jam/disclosure/DisclosurePanel.java | 167 ++
.../jam/disclosure/images/disclosureDownNormal.png | Bin 0 -> 239 bytes
.../disclosure/images/disclosureDownPressed.png | Bin 0 -> 235 bytes
.../jam/disclosure/images/disclosureRightDown.png | Bin 0 -> 271 bytes
.../disclosure/images/disclosureRightNormal.png | Bin 0 -> 239 bytes
.../disclosure/images/disclosureRightPressed.png | Bin 0 -> 244 bytes
.../jam/disclosure/images/titleBackground.png | Bin 0 -> 180 bytes
src/org/virion/jam/framework/AboutBox.java | 119 ++
src/org/virion/jam/framework/AbstractFrame.java | 327 ++++
src/org/virion/jam/framework/Application.java | 304 +++
src/org/virion/jam/framework/AuxilaryFrame.java | 63 +
src/org/virion/jam/framework/Command.java | 21 +
.../jam/framework/DefaultEditMenuFactory.java | 57 +
.../jam/framework/DefaultFileMenuFactory.java | 91 +
.../jam/framework/DefaultHelpMenuFactory.java | 53 +
.../jam/framework/DefaultMenuBarFactory.java | 97 +
src/org/virion/jam/framework/DocumentFrame.java | 159 ++
.../virion/jam/framework/DocumentFrameFactory.java | 12 +
src/org/virion/jam/framework/Exportable.java | 12 +
src/org/virion/jam/framework/MenuBarFactory.java | 18 +
src/org/virion/jam/framework/MenuFactory.java | 36 +
.../virion/jam/framework/MultiDocApplication.java | 231 +++
.../jam/framework/MultiDocMenuBarFactory.java | 27 +
src/org/virion/jam/framework/RecentFileList.java | 196 ++
.../virion/jam/framework/SingleDocApplication.java | 93 +
.../jam/framework/SingleDocMenuBarFactory.java | 27 +
src/org/virion/jam/html/HTMLViewer.java | 25 +
src/org/virion/jam/html/SimpleLinkListener.java | 33 +
src/org/virion/jam/layouts/CompassLayout.java | 363 ++++
src/org/virion/jam/mac/MacFileMenuFactory.java | 98 +
src/org/virion/jam/mac/MacHelpMenuFactory.java | 51 +
src/org/virion/jam/mac/MacWindowMenuFactory.java | 39 +
src/org/virion/jam/mac/Utils.java | 60 +
src/org/virion/jam/maconly/OSXAdapter.java | 85 +
src/org/virion/jam/panels/ActionPanel.java | 121 ++
src/org/virion/jam/panels/AddRemovePanel.java | 180 ++
src/org/virion/jam/panels/OptionsPanel.java | 138 ++
src/org/virion/jam/panels/RuleModel.java | 43 +
src/org/virion/jam/panels/RulesPanel.java | 201 ++
src/org/virion/jam/panels/SearchPanel.java | 281 +++
src/org/virion/jam/panels/SearchPanelListener.java | 26 +
src/org/virion/jam/panels/StatusBar.java | 28 +
src/org/virion/jam/panels/StatusListener.java | 17 +
src/org/virion/jam/panels/StatusPanel.java | 240 +++
src/org/virion/jam/panels/StatusProvider.java | 144 ++
.../jam/panels/images/action/actionButton.png | Bin 0 -> 808 bytes
.../panels/images/action/actionButtonInactive.png | Bin 0 -> 759 bytes
.../panels/images/action/actionButtonPressed.png | Bin 0 -> 786 bytes
.../jam/panels/images/activity/activity1.png | Bin 0 -> 474 bytes
.../jam/panels/images/activity/activity10.png | Bin 0 -> 484 bytes
.../jam/panels/images/activity/activity11.png | Bin 0 -> 475 bytes
.../jam/panels/images/activity/activity12.png | Bin 0 -> 474 bytes
.../jam/panels/images/activity/activity2.png | Bin 0 -> 479 bytes
.../jam/panels/images/activity/activity3.png | Bin 0 -> 487 bytes
.../jam/panels/images/activity/activity4.png | Bin 0 -> 472 bytes
.../jam/panels/images/activity/activity5.png | Bin 0 -> 485 bytes
.../jam/panels/images/activity/activity6.png | Bin 0 -> 478 bytes
.../jam/panels/images/activity/activity7.png | Bin 0 -> 468 bytes
.../jam/panels/images/activity/activity8.png | Bin 0 -> 479 bytes
.../jam/panels/images/activity/activity9.png | Bin 0 -> 475 bytes
src/org/virion/jam/panels/images/add/addButton.png | Bin 0 -> 394 bytes
.../jam/panels/images/add/addButtonInactive.png | Bin 0 -> 397 bytes
.../jam/panels/images/add/addButtonPressed.png | Bin 0 -> 388 bytes
.../virion/jam/panels/images/network/network1.png | Bin 0 -> 524 bytes
.../virion/jam/panels/images/network/network2.png | Bin 0 -> 837 bytes
.../virion/jam/panels/images/network/network3.png | Bin 0 -> 857 bytes
.../virion/jam/panels/images/network/network4.png | Bin 0 -> 814 bytes
.../virion/jam/panels/images/network/network5.png | Bin 0 -> 541 bytes
.../virion/jam/panels/images/network/network6.png | Bin 0 -> 763 bytes
.../virion/jam/panels/images/network/network7.png | Bin 0 -> 723 bytes
.../virion/jam/panels/images/network/network8.png | Bin 0 -> 739 bytes
.../virion/jam/panels/images/plusminus/minus.png | Bin 0 -> 477 bytes
.../jam/panels/images/plusminus/minusPressed.png | Bin 0 -> 482 bytes
.../jam/panels/images/plusminus/minusRollover.png | Bin 0 -> 482 bytes
.../virion/jam/panels/images/plusminus/plus.png | Bin 0 -> 491 bytes
.../jam/panels/images/plusminus/plusPressed.png | Bin 0 -> 506 bytes
.../jam/panels/images/plusminus/plusRollover.png | Bin 0 -> 494 bytes
.../jam/panels/images/remove/removeButton.png | Bin 0 -> 348 bytes
.../panels/images/remove/removeButtonInactive.png | Bin 0 -> 345 bytes
.../panels/images/remove/removeButtonPressed.png | Bin 0 -> 334 bytes
src/org/virion/jam/panels/images/search/find.png | Bin 0 -> 620 bytes
.../virion/jam/panels/images/search/findPopup.png | Bin 0 -> 712 bytes
src/org/virion/jam/panels/images/search/stop.png | Bin 0 -> 339 bytes
.../jam/panels/images/search/stopPressed.png | Bin 0 -> 397 bytes
.../jam/panels/images/search/stopRollover.png | Bin 0 -> 365 bytes
src/org/virion/jam/panels/images/status/resume.png | Bin 0 -> 523 bytes
.../jam/panels/images/status/resumePressed.png | Bin 0 -> 539 bytes
.../jam/panels/images/status/resumeRollover.png | Bin 0 -> 554 bytes
src/org/virion/jam/panels/images/status/reveal.png | Bin 0 -> 518 bytes
.../jam/panels/images/status/revealPressed.png | Bin 0 -> 490 bytes
.../jam/panels/images/status/revealRollover.png | Bin 0 -> 547 bytes
src/org/virion/jam/panels/images/status/stop.png | Bin 0 -> 339 bytes
.../jam/panels/images/status/stopPressed.png | Bin 0 -> 397 bytes
.../jam/panels/images/status/stopRollover.png | Bin 0 -> 365 bytes
.../virion/jam/panels/images/status/warning.png | Bin 0 -> 231 bytes
.../jam/panels/images/status/warningPressed.png | Bin 0 -> 509 bytes
.../jam/panels/images/status/warningRollover.png | Bin 0 -> 496 bytes
.../virion/jam/preferences/PreferencesDialog.java | 122 ++
.../virion/jam/preferences/PreferencesSection.java | 20 +
src/org/virion/jam/table/AdvancedTableUI.java | 42 +
src/org/virion/jam/table/ColorEditor.java | 78 +
src/org/virion/jam/table/ColorRenderer.java | 50 +
src/org/virion/jam/table/HeaderRenderer.java | 52 +
src/org/virion/jam/table/RealNumberCellEditor.java | 46 +
src/org/virion/jam/table/TableRenderer.java | 57 +
.../virion/jam/table/WholeNumberCellEditor.java | 46 +
src/org/virion/jam/toolbar/GenericToolbarItem.java | 40 +
src/org/virion/jam/toolbar/Toolbar.java | 167 ++
src/org/virion/jam/toolbar/ToolbarAction.java | 51 +
src/org/virion/jam/toolbar/ToolbarButton.java | 57 +
src/org/virion/jam/toolbar/ToolbarItem.java | 10 +
src/org/virion/jam/toolbar/ToolbarOptions.java | 29 +
src/org/virion/jam/util/BrowserLauncher.java | 617 +++++++
src/org/virion/jam/util/IconUtils.java | 169 ++
src/org/virion/jam/util/JExceptionDialog.java | 97 +
src/org/virion/jam/util/ListItemView.java | 18 +
src/org/virion/jam/util/LongTask.java | 73 +
src/org/virion/jam/util/LongTaskMonitor.java | 60 +
src/org/virion/jam/util/PrintUtilities.java | 137 ++
src/org/virion/jam/util/SimpleListener.java | 10 +
src/org/virion/jam/util/SimpleListenerManager.java | 47 +
src/org/virion/jam/util/SimpleLongTask.java | 42 +
src/org/virion/jam/util/SwingWorker.java | 148 ++
src/org/virion/jam/util/TaskListener.java | 9 +
src/org/virion/jam/util/Utils.java | 83 +
src/org/xml/sax/AttributeList.java | 193 ++
src/org/xml/sax/Attributes.java | 257 +++
src/org/xml/sax/ContentHandler.java | 419 +++++
src/org/xml/sax/DTDHandler.java | 117 ++
src/org/xml/sax/DocumentHandler.java | 232 +++
src/org/xml/sax/EntityResolver.java | 119 ++
src/org/xml/sax/ErrorHandler.java | 139 ++
src/org/xml/sax/HandlerBase.java | 369 ++++
src/org/xml/sax/InputSource.java | 336 ++++
src/org/xml/sax/Locator.java | 136 ++
src/org/xml/sax/Parser.java | 209 +++
src/org/xml/sax/SAXException.java | 153 ++
src/org/xml/sax/SAXNotRecognizedException.java | 53 +
src/org/xml/sax/SAXNotSupportedException.java | 53 +
src/org/xml/sax/SAXParseException.java | 269 +++
src/org/xml/sax/XMLFilter.java | 65 +
src/org/xml/sax/XMLReader.java | 404 ++++
src/org/xml/sax/ext/Attributes2.java | 132 ++
src/org/xml/sax/ext/Attributes2Impl.java | 301 +++
src/org/xml/sax/ext/DeclHandler.java | 146 ++
src/org/xml/sax/ext/DefaultHandler2.java | 130 ++
src/org/xml/sax/ext/EntityResolver2.java | 197 ++
src/org/xml/sax/ext/LexicalHandler.java | 212 +++
src/org/xml/sax/ext/Locator2.java | 75 +
src/org/xml/sax/ext/Locator2Impl.java | 101 +
src/org/xml/sax/ext/package.html | 46 +
src/org/xml/sax/helpers/AttributeListImpl.java | 312 ++++
src/org/xml/sax/helpers/AttributesImpl.java | 618 +++++++
src/org/xml/sax/helpers/DefaultHandler.java | 467 +++++
src/org/xml/sax/helpers/LocatorImpl.java | 214 +++
src/org/xml/sax/helpers/NamespaceSupport.java | 835 +++++++++
src/org/xml/sax/helpers/NewInstance.java | 79 +
src/org/xml/sax/helpers/ParserAdapter.java | 1046 +++++++++++
src/org/xml/sax/helpers/ParserFactory.java | 129 ++
src/org/xml/sax/helpers/XMLFilterImpl.java | 713 +++++++
src/org/xml/sax/helpers/XMLReaderAdapter.java | 538 ++++++
src/org/xml/sax/helpers/XMLReaderFactory.java | 202 ++
src/org/xml/sax/helpers/package.html | 11 +
src/org/xml/sax/package.html | 297 +++
src/phyutility/concat/Concat.java | 278 +++
src/phyutility/consensus/Consensus.java | 41 +
src/phyutility/drb/WwdEmbedded.java | 236 +++
src/phyutility/drb/WwdUtils.java | 54 +
src/phyutility/jebl2jade/NexusToJade.java | 73 +
src/phyutility/leafstability/Calculator.java | 23 +
src/phyutility/leafstability/Runner.java | 113 ++
src/phyutility/lineagemovement/Main.java | 200 ++
src/phyutility/mainrunner/Main.java | 1566 ++++++++++++++++
src/phyutility/mainrunner/TestFuncs.java | 1497 +++++++++++++++
src/phyutility/mainrunner/Utils.java | 80 +
src/phyutility/ncbi/EFetchContentHandler.java | 168 ++
src/phyutility/ncbi/ESearchContentHandler.java | 76 +
src/phyutility/ncbi/Einfo.java | 84 +
src/phyutility/pruner/Pruner.java | 49 +
src/phyutility/treesupport/Runner.java | 124 ++
src/phyutility/trimsites/TrimSites.java | 208 +++
628 files changed, 85484 insertions(+)
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..32765cd
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,20 @@
+<project name="phyutility" basedir=".">
+ <target name="clean">
+ <delete dir="build"/>
+ </target>
+ <target name="compile">
+ <mkdir dir="build/classes"/>
+ <javac srcdir="src" destdir="build/classes"/>
+ </target>
+ <target name="jar">
+ <mkdir dir="build/jar"/>
+ <jar destfile="build/jar/phyutility.jar" basedir="build/classes">
+ <manifest>
+ <attribute name="Main-Class" value="phyutility.mainrunner.Main"/>
+ </manifest>
+ </jar>
+ </target>
+ <target name="run">
+ <java jar="build/jar/phyutility.jar" fork="true"/>
+ </target>
+</project>
\ No newline at end of file
diff --git a/examples/test.aln b/examples/test.aln
new file mode 100644
index 0000000..197b272
--- /dev/null
+++ b/examples/test.aln
@@ -0,0 +1,155 @@
+>49607
+------------------------------------------------------------
+-------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCA
+CATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAATGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAATGGCGCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCTGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGGATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGGATCGAATAAAATATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCGGGCTCGGAATTCTTAGAA
+GACTTCTTTACAGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCCTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAACAAAAGTGTAGATTGCTTTCTATTCTGAAACG
+TTGATGTAGCACGGAATAAGGGTGAAATCAAAATAAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTTAGATGTATACATAGGGAAAGCCGTGTGCAACGA
+AAAGTGCAAGCACGGTTTGGGGAGAGGTTTTTACTTATTTAACAAGGAAATTAT
+>245821
+ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCTTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGCGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAG-----------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>213829
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+---------------------------------TCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTCTTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCAT---------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>180558
+ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATTGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAG-ACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAAAAAAAGTGTAAATTGCTTTCTATTCTGAAACG
+TTGATGTAGCATGGAATAAGGGTGAAATCAAAATCAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTT---------------------------------
+------------------------------------------------------
+>180557
+ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATTGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAG-ACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAAAAAAAGTGTAAATTGCTTTCTATTCTGAAACG
+TTGATGTAGCATGGAATAAGGGTGAAATCAAAATCAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTT---------------------------------
+------------------------------------------------------
diff --git a/examples/test.con b/examples/test.con
new file mode 100644
index 0000000..6a5b3a1
--- /dev/null
+++ b/examples/test.con
@@ -0,0 +1,16 @@
+#NEXUS
+begin taxa;
+ dimensions ntax=6;
+ taxlabels
+ one
+ two
+ five
+ three
+ four
+ six
+;
+end;
+
+begin trees;
+ tree [&r] tree_1 = (((one:1.0,two:1.0)[&"Consensus support(%)"=100.0]:1.0,(five:0.6,three:1.0)[&"Consensus support(%)"=60.0]:1.0)[&"Consensus support(%)"=60.0]:1.0,(four:1.4,six:1.0)[&"Consensus support(%)"=60.0]:1.0);
+end;
diff --git a/examples/test.fasta b/examples/test.fasta
new file mode 100644
index 0000000..68b84a2
--- /dev/null
+++ b/examples/test.fasta
@@ -0,0 +1,155 @@
+>49607
+------------------------------------------------------------
+-------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCA
+CATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAATGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAATGGCGCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCTGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGGATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGGATCGAATAAAATATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCGGGCTCGGAATTCTTAGAA
+GACTTCTTTACAGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCCTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAACAAAAGTGTAGATTGCTTTCTATTCTGAAACG
+TTGATGTAGCACGGAATAAGGGTGAAATCAAAATAAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTTAGATGTATACATAGGGAAAGCCGTGTGCAACGA
+AAAGTGCAAGCACGGTTTGGGGAGAGGTTTTTACTTATTTAACAAGGAAATTAT
+>245821
+ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCTTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGCGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAG-----------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>213829
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+---------------------------------TCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTCTTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCAT---------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>180558
+ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATTGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAG-ACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAAAAAAAGTGTAAATTGCTTTCTATTCTGAAACG
+TTGATGTAGCATGGAATAAGGGTGAAATCAAAATCAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTT---------------------------------
+------------------------------------------------------
+>180557
+ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATTGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAG-ACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAAAAAAAGTGTAAATTGCTTTCTATTCTGAAACG
+TTGATGTAGCATGGAATAAGGGTGAAATCAAAATCAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTT---------------------------------
+------------------------------------------------------
\ No newline at end of file
diff --git a/examples/test.gb b/examples/test.gb
new file mode 100644
index 0000000..67fab03
--- /dev/null
+++ b/examples/test.gb
@@ -0,0 +1,44 @@
+>gi|149207768|gb|EF611058.1| Lonicera tatarica internal transcribed spacer 1, partial sequence; and 5.8S ribosomal RNA gene and internal transcribed spacer 2, complete sequence
+TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTACACCGGGACGCGGGCGGGGGGCGCGTCAG
+CCCCCCCGTCGGCGCTCCCATGGTCCGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGA
+TCCGCGCCAAGGAACAACAAACAGAAGGGCGTGCCCCCCGCTGCCCCGTCCGCGGTGCGCGCGGGGGTGC
+CTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTA
+GCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCC
+TGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCC
+ACAGGGCCGCGGTCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATC
+GAGTCCCCGGCGGCGGACGTCACGACGAGTGGTGGTCGAAACATTCCTCTTATCACGTCGTGCGGTTCCC
+CGTCGCTCGGGCGGCCGAGTGACCCTGACGCGTCGTCTTCCGACGGCGCTCCGAC
+
+>gi|149207767|gb|EF611057.1| Lonicera tatarica var. micrantha internal transcribed spacer 1, partial sequence; and 5.8S ribosomal RNA gene and internal transcribed spacer 2, complete sequence
+TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTACACCGGGACGCGGGCGGGGGGCGCGTCAG
+CCCCCCCGTCGGCGCTCCCATGGTCCGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGA
+TCCGCGCCAAGGAACAACAAACAGAAGGGCGTGCCCCCCGCTGCCCCGTCCGCGGTGCGCGCGGGGGTGC
+CTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTA
+GCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCC
+CGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCC
+ACAGGGCCGCGGTCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATC
+GAGTCCCCGGCGGCGGACGTCACGACGAGTGGTGGTCGAAACATTCCTCTTATCACGTCGTGCGGTTCCC
+CGTCGCTCGGGCGGCCGAGTGACCCTGACGCGTCGTCTTCCGACGGCGCTCCGAC
+
+>gi|149207766|gb|EF611056.1| Lonicera tatarica cultivar Fanguo internal transcribed spacer 1, partial sequence; and 5.8S ribosomal RNA gene and internal transcribed spacer 2, complete sequence
+TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTTCACCGGGACGCGGGCAGGGGGCGCGTCAG
+CCCCCCCGTCGGCGCCCCCATGGTCTGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGA
+TCCGCGCCAAGGAACAACAAACAGAAGGGCGCGCCCCCCGTTGCCCCGTTCGCGGTGCGCGCGGGGGCGC
+CTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCCCTCGCATCGATGAAGAACGTA
+GCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCC
+CGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCC
+ACAGGGCCGCGGGCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATC
+GAGTCCCCGGCGGCGGGCGTCACGACGAGTGGTGGTCGAAACATTCCTCTTATCACGTCGTGCGGTTCCC
+CGTCGCTCGGGCGGCCGAGTGACCCTGACGCGTCGTCTTCCGACGGCGCTCCGAC
+
+>gi|149207765|gb|EF611055.1| Lonicera prostrata internal transcribed spacer 1, partial sequence; 5.8S ribosomal RNA gene and internal transcribed spacer 2, complete sequence; and 28S ribosomal RNA gene, partial sequence
+TCGAAACCTACACAGCTGAACGATCCGCGAAAACGTTCGTAGAGCGGGACGCGGGCAGGGGGCACATCAG
+CCCCCCGTCAGAGCTCCCATGGTCGGGGGGCTCACCGGCTCCTCTGCCCAAAACCAAACCCCGGGGCGAT
+ACGCACCAAGGAACAACAAACAGAAGGGCGTGCGCCCTGTTTCCCCGTTTGCGGTGCGCCGTGGGGTGCC
+TCGCTTATTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTAG
+CGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCC
+GAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGTGTCTCCCCCCACGCTGCGTCCCAC
+AGGGTCGCGGGCGGCGGGGGGTGCGAAAAATGGCCTCTCGTGCCCCTGGGCGCGGCTGGCCCAAAATTGA
+ATCCCTGGCGGCGGACGTCACGACGAGTGGTGGTTGAAACAATCCTCTTATCAGGTTGTGCGGTTCCCCA
+TCACTCGGGCGTCCGAGTGACCTTGACACATAGTCTTCTGACAGCACTCCGACCA
+
diff --git a/examples/test.nex b/examples/test.nex
new file mode 100644
index 0000000..f6583b3
--- /dev/null
+++ b/examples/test.nex
@@ -0,0 +1,14 @@
+#NEXUS
+BEGIN DATA;
+ [test.aln_gene1 1-1794 ]
+ DIMENSIONS NTAX=5 NCHAR=1794;
+ FORMAT DATATYPE = DNA GAP = - MISSING = ?;
+ MATRIX
+ 49607 -------------------------------------------------------------------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCACATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAAC [...]
+ 245821 ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 213829 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [...]
+ 180558 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 180557 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+;
+END;
+
diff --git a/examples/test.tre b/examples/test.tre
new file mode 100644
index 0000000..a8441d1
--- /dev/null
+++ b/examples/test.tre
@@ -0,0 +1 @@
+(((one, two),(three,four)),(five,six));
\ No newline at end of file
diff --git a/examples/test2.aln b/examples/test2.aln
new file mode 100644
index 0000000..8772dfa
--- /dev/null
+++ b/examples/test2.aln
@@ -0,0 +1,124 @@
+>49607
+------------------------------------------------------------
+-------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCA
+CATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAATGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAATGGCGCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCTGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGGATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGGATCGAATAAAATATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCGGGCTCGGAATTCTTAGAA
+GACTTCTTTACAGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCCTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAACAAAAGTGTAGATTGCTTTCTATTCTGAAACG
+TTGATGTAGCACGGAATAAGGGTGAAATCAAAATAAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTTAGATGTATACATAGGGAAAGCCGTGTGCAACGA
+AAAGTGCAAGCACGGTTTGGGGAGAGGTTTTTACTTATTTAACAAGGAAATTAT
+>245821
+ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCTTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGCGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAG-----------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>213829
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+---------------------------------TCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTCTTTCCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATCGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAGTACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCAT---------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------------
+------------------------------------------------------
+>180558
+ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATAT
+CCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCG
+CATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTA
+ATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAAT
+GTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTC
+ATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTC
+AAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCA
+CATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTT
+CAAACTCTTCGCTACTGGGTAAAAGATGCCCCCTCTTTGCATTTATTACGATTCTTTCTC
+CACGAGTATCCTAATTTGACTAGTCTTATTATTCCAAAGAAAGCCGGTTCTTCTTTTTCA
+AAACGAAATCAAAGATTATTTTTCTTTCTATATAATTCTCATGTATGTGAATACGAATTC
+ATCTTCGTCTTTCTCCGTAACCAATCTTCTCATTTACGATCAACATCTTCTGGAGCCTTT
+CTTGAACGAATATATTTCTATGAAAAAATAGAACATCTTGTAGAAGTCTTTGCTAAGGAT
+TTTCAAGCCAATCTATGGTTGTTCAAGGATCCTTTCATGCATTATGTTAGGTATCAAGGA
+AAGTCAATTCTCGCTTCAAAGGGGACCTTTCTTTTGATGAATAAATGGAAATATTACTTT
+GTACGTTTCTGGCAATGTCATTTTTACCAGTGGTTTCAACCAGGAAGGATTTCTATAAAC
+CAATTATCCAAACATTCGCTCGACCTTCTGGGTTATCTTTCAAGTGTGCGGCTAAACCCC
+TTAACGGCTCGCAGTCAAATGCTAGAAAATGCATTTCTAATTGATAATGCAGGTAAGAAG
+TTCGATACCATTGTTCCAATTAGTCCTCTGATTGGATCATTGGCTAAAGCGAAATTTTGT
+AACGTATTAGGGCATCCTGTTAGTAAGGTGCTTTGGGCCGATTTATCAGATTCTGAGATT
+ATTGACCGATTTGGGCGTATATGCAGAAATCTTTCTCATTATCATAGCGGATCTTCACAA
+AAAAAGAGTTTGTATCGAATAAAGTATATACTTCGACTTTCTTGTGCTAGAACTTTGGCT
+CGTAAACACAAAAG-ACTGTACGTGCTTTTTTGAAAAGATCAGGCTCGGAATTCTTAGAA
+AACTTCTTTACGGCGGAAGAACAGGTTCTTTTCTTGACTTTCCCAAGAGCTTCTTCTACT
+TCGCAGAGGATATATAGAAGGACGATTTGGTATTTGGATATTATTTGTATCAATGATTTG
+GCCAATCATGAATGATTCGTTAGGAAACTTTGTAATTGGAAATTCTCCTTTAAATGATGA
+ATATAAATAATGATAGAGATAATGATAAAAAAAGTGTAAATTGCTTTCTATTCTGAAACG
+TTGATGTAGCATGGAATAAGGGTGAAATCAAAATCAAACTAATATTCCACTTTTTGAAAA
+TCTTGTCTAAGCAAGGAGCTGGCTTTT---------------------------------
+------------------------------------------------------
diff --git a/examples/test_50.nex b/examples/test_50.nex
new file mode 100644
index 0000000..da6313a
--- /dev/null
+++ b/examples/test_50.nex
@@ -0,0 +1,23 @@
+#NEXUS
+begin taxa;
+ dimensions ntax=5;
+ taxlabels
+ 49607
+ 180557
+ 213829
+ 180558
+ 245821
+;
+end;
+
+begin characters;
+ dimensions nchar=1706;
+ format datatype=dna missing=? gap=-;
+ matrix
+ 49607 -------------------------------------------------------------------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCACATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAAC [...]
+ 180557 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 213829 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [...]
+ 180558 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 245821 ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+;
+end;
diff --git a/examples/test_all.aln b/examples/test_all.aln
new file mode 100644
index 0000000..a9c2189
--- /dev/null
+++ b/examples/test_all.aln
@@ -0,0 +1,14 @@
+#NEXUS
+BEGIN DATA;
+ [test.aln_gene1 1-1794 test2.aln_gene2 1795-3588 ]
+ DIMENSIONS NTAX=5 NCHAR=3588;
+ FORMAT DATATYPE = DNA GAP = - MISSING = ?;
+ MATRIX
+ 49607 -------------------------------------------------------------------------------ATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATCTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTACTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTGTTTTTGGGGCGCAATAAGAATTTGTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCGTAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGATAAATTTTCACATTTAAACTATGTATTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAAC [...]
+ 245821 ATGGAGGAATTCAAAAGAGATTTAGAGCGAGATGGGTCTCAACAACATTACTTCTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTGGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 213829 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [...]
+ 180558 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+ 180557 ATGGAGGAATTCAAAAGAGATTTCGAGCGAGATGGGTCTCAACAACATTACTTTTTATATCCACTTATCTTTCAGGAGTATATTTATGCACTTGCTTATGATCATGGTTTAAATAGAGCGCATTTGTTGGAAAATGTAGGGTATGACAATAAATCTAGCTTACTATTTGTGAAACGTTTAATTATTCGAATGTATCAAAAGAATCATTTGATTTTTTCTGTTAAGGATTCTAACCAAAATGTATTTTTGGGGCGCAATAAGAATTTTTATTCTCAAATGATATCAGAGGGATTTGCAGTCATTGTAGAAATTCCATTTTCTCTACGCTTTTTATCTTCCCTAGAAAAGAAAGGGAGAGTCAAATCTCATAATTTACGATCAATTCATTCAGTATTTCCTTTTTTAGAGGACAAATTTTCACATTTAAACTATGTAGTAGATATACTAATACCTTACTCAGCCCATTTGGAAATTCTGGTTCAAA [...]
+;
+END;
+
diff --git a/examples/test_all.tre b/examples/test_all.tre
new file mode 100644
index 0000000..e2562f5
--- /dev/null
+++ b/examples/test_all.tre
@@ -0,0 +1,5 @@
+(((one, two),(three,four)),(five,six));
+(((one, two),(three,five)),(four,six));
+(((one, two),(three,five)),(four,six));
+(((one, two),(three,five)),(four,six));
+(((one, two),(three,four)),(five,six));
\ No newline at end of file
diff --git a/examples/test_gb1.fasta b/examples/test_gb1.fasta
new file mode 100644
index 0000000..49ad16d
--- /dev/null
+++ b/examples/test_gb1.fasta
@@ -0,0 +1,12 @@
+>149207768
+>TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTACACCGGGACGCGGGCGGGGGGCGCGTCAGCCCCCCCGTCGGCGCTCCCATGGTCCGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGATCCGCGCCAAGGAACAACAAACAGAAGGGCGTGCCCCCCGCTGCCCCGTCCGCGGTGCGCGCGGGGGTGCCTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTAGCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCTGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCCACAGGGCCGCGGTCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATCG [...]
+
+>149207767
+>TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTACACCGGGACGCGGGCGGGGGGCGCGTCAGCCCCCCCGTCGGCGCTCCCATGGTCCGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGATCCGCGCCAAGGAACAACAAACAGAAGGGCGTGCCCCCCGCTGCCCCGTCCGCGGTGCGCGCGGGGGTGCCTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTAGCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCCACAGGGCCGCGGTCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATCG [...]
+
+>149207766
+>TCGAAACCTGCACAGCAGAACGACCCGCGAACACGTTCGTTCACCGGGACGCGGGCAGGGGGCGCGTCAGCCCCCCCGTCGGCGCCCCCATGGTCTGGGGGCTCACCGGCTCCCGGGCCCAAAACCGAACCCCGGCGCGATCCGCGCCAAGGAACAACAAACAGAAGGGCGCGCCCCCCGTTGCCCCGTTCGCGGTGCGCGCGGGGGCGCCTCGCCTCTTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCCCTCGCATCGATGAAGAACGTAGCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGCGTCGCCCCCCCACCCCGCGTCCCACAGGGCCGCGGGCGGCGGGGGGTGCGGACAATGGCCTCCCGTGCCCCCGGGCGCGGCTGGCCCAAAATCG [...]
+
+>149207765
+>TCGAAACCTACACAGCTGAACGATCCGCGAAAACGTTCGTAGAGCGGGACGCGGGCAGGGGGCACATCAGCCCCCCGTCAGAGCTCCCATGGTCGGGGGGCTCACCGGCTCCTCTGCCCAAAACCAAACCCCGGGGCGATACGCACCAAGGAACAACAAACAGAAGGGCGTGCGCCCTGTTTCCCCGTTTGCGGTGCGCCGTGGGGTGCCTCGCTTATTTCGAAACACAAACGACTCTCGGCAACGGATATCTCGGCTCTCGCATCGATGAAGAACGTAGCGAAATGCGATACTTGGTGTGAATTGCAGAATCCCGTGAACCATCGAGTCTTTGAACGCAAGTTGCGCCCGAAGCCATTAGGCCGAGGGCACGTCTGCCTGGGCGTCACGCATCGTGTCTCCCCCCACGCTGCGTCCCACAGGGTCGCGGGCGGCGGGGGGTGCGAAAAATGGCCTCTCGTGCCCCTGGGCGCGGCTGGCCCAAAATTGAA [...]
+
diff --git a/examples/test_lm.tre b/examples/test_lm.tre
new file mode 100644
index 0000000..d56df23
--- /dev/null
+++ b/examples/test_lm.tre
@@ -0,0 +1,5 @@
+#NEXUS
+BEGIN TREES;
+ TREE tree0 = (((one:1.0,two:1.0):1.0,(five_0.6:0.6,three:1.0):1.0):1.0,(four_0.4:1.4,six:1.0):1.0);
+END;
+
diff --git a/examples/test_pr.tre b/examples/test_pr.tre
new file mode 100644
index 0000000..915e984
--- /dev/null
+++ b/examples/test_pr.tre
@@ -0,0 +1 @@
+(((three:0.0,four:0.0):0.0,two:0.0):0.0,(five:0.0,six:0.0):0.0);
diff --git a/examples/test_rr.tre b/examples/test_rr.tre
new file mode 100644
index 0000000..e0ee461
--- /dev/null
+++ b/examples/test_rr.tre
@@ -0,0 +1 @@
+((one:0.0,two:0.0):0.0,((three:0.0,four:0.0):0.0,(five:0.0,six:0.0):0.0):0.0);
diff --git a/examples/test_ts.tre b/examples/test_ts.tre
new file mode 100644
index 0000000..a59a00e
--- /dev/null
+++ b/examples/test_ts.tre
@@ -0,0 +1,5 @@
+#NEXUS
+BEGIN TREES;
+ TREE tree0 = (((one:1.0,two:1.0)1.0:1.0,(five:0.6,three:1.0)0.6:1.0)0.6:1.0,(four:1.4,six:1.0)0.6:1.0)1.0;
+END;
+
diff --git a/examples/test_vert.nex b/examples/test_vert.nex
new file mode 100644
index 0000000..b6ef310
--- /dev/null
+++ b/examples/test_vert.nex
@@ -0,0 +1,16 @@
+#NEXUS
+begin taxa;
+ dimensions ntax=6;
+ taxlabels
+ one
+ two
+ five
+ three
+ four
+ six
+;
+end;
+
+begin trees;
+ tree [&r] tree_1 = (((one:1.0,two:1.0):1.0,(three:1.0,four:1.0):1.0):1.0,(five:1.0,six:1.0):1.0);
+end;
diff --git a/gpl.txt b/gpl.txt
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/gpl.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. 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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. 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.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/manual.pdf b/manual.pdf
new file mode 100644
index 0000000..d227c77
Binary files /dev/null and b/manual.pdf differ
diff --git a/phyutility b/phyutility
new file mode 100755
index 0000000..999a8c9
--- /dev/null
+++ b/phyutility
@@ -0,0 +1 @@
+java -Xmx2g -jar phyutility.jar $*
diff --git a/phyutility.jar b/phyutility.jar
new file mode 100644
index 0000000..3e8536e
Binary files /dev/null and b/phyutility.jar differ
diff --git a/phyutility_sm.png b/phyutility_sm.png
new file mode 100644
index 0000000..5b8b06a
Binary files /dev/null and b/phyutility_sm.png differ
diff --git a/src/jade/data/AbstractAlignment.java b/src/jade/data/AbstractAlignment.java
new file mode 100644
index 0000000..faacdd4
--- /dev/null
+++ b/src/jade/data/AbstractAlignment.java
@@ -0,0 +1,207 @@
+package jade.data;
+
+import java.util.*;
+import java.io.*;
+
+abstract public class AbstractAlignment {
+ /*
+ * the character for an unknown character
+ */
+ static char UNKNOWN = DataType.UNKNOWN_CHARACTER;
+
+ static char GAP = DataType.DEFAULT_GAP_CHARACTER;
+
+ /** Characters that might be used as gaps */
+ static String GAPS = "_-?.";
+
+ /*
+ * abstract constructor
+ */
+ public AbstractAlignment() {
+ }
+
+ /** number of sequences */
+ protected int numSeqs;
+
+ /** length of each sequence */
+ protected int numSites;
+
+ /** sequence identifiers */
+ protected ArrayList<String> seqIDs;
+
+ /** data type */
+ private DataType dataType;
+
+ /** sequence alignment at (sequence, site) */
+ abstract public char getData(int seq, int site);
+
+ /**
+ * returns true if there is a gap in the give position.
+ */
+ public boolean isGap(int seq, int site) {
+ return dataType.isGapChar(getData(seq, site));
+ }
+
+ public void guessDataType() {
+ getSuitableInstance();
+ }
+
+ private void getSuitableInstance() {
+ // count A, C, G, T, U, N
+ long numNucs = 0;
+ long numChars = 0;
+ long numBins = 0;
+ for (int i = 0; i < numSeqs; i++) {
+ for (int j = 0; j < numSites; j++) {
+ char c = getData(i, j);
+
+ if (c == 'A' || c == 'C' || c == 'G' || c == 'T' || c == 'U'
+ || c == 'N')
+ numNucs++;
+
+ if (c != '-' && c != '?')
+ numChars++;
+
+ if (c == '0' || c == '1')
+ numBins++;
+ }
+ }
+
+ if (numChars == 0)
+ numChars = 1;
+
+ // more than 85 % frequency advocates nucleotide data
+ if ((double) numNucs / (double) numChars > 0.85) {
+ dataType = new NucleotideDataType();
+ } else if ((double) numBins / (double) numChars > 0.2) {
+ dataType = new BinaryDataType();
+ }
+ }
+
+ /**
+ * Same as getDataType().getChar(state)
+ */
+ protected final char getChar(int state) {
+ return dataType.getChar(state);
+ }
+
+ /**
+ * Same as getDataType().getState(char)
+ */
+ protected final int getState(char c) {
+ return dataType.getState(c);
+ }
+
+ /**
+ * Same as getDataType().isUnknownState(state)
+ */
+ protected final boolean isUnknownState(int state) {
+ return dataType.isUnknownState(state);
+ }
+
+ /** Returns the datatype of this alignment */
+ public final DataType getDataType() {
+ return dataType;
+ }
+
+ /** Sets the datatype of this alignment */
+ public final void setDataType(DataType d) {
+ dataType = d;
+ }
+
+ /** returns representation of this alignment as a string */
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ // AlignmentUtils.print(this, new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ // interface Report
+
+ public void report(PrintWriter out) {
+ // AlignmentUtils.report(this, out);
+ }
+
+ /**
+ * Fills a [numsequences][length] matrix with indices. Each index represents
+ * the sequence state, -1 means a gap.
+ */
+ public int[][] getStates() {
+
+ int[][] indices = new int[numSeqs][numSites];
+
+ for (int i = 0; i < numSeqs; i++) {
+ // int seqcounter = 0;
+
+ for (int j = 0; j < numSites; j++) {
+
+ indices[i][j] = dataType.getState(getData(i, j));
+
+ if (indices[i][j] >= dataType.getNumStates()) {
+ indices[i][j] = -1;
+ }
+ }
+ }
+
+ return indices;
+ }
+
+ /**
+ * Return number of sites in this alignment
+ */
+ public final int getLength() {
+ return numSites;
+ }
+
+ /**
+ * Return number of sequences in this alignment
+ */
+ public final int getSequenceCount() {
+ return numSeqs;
+ }
+
+ /**
+ * Return number of sites for each sequence in this alignment
+ *
+ * @note for people who like accessor methods over public instance
+ * variables...
+ */
+ public final int getSiteCount() {
+ return numSites;
+ }
+
+ /**
+ * Returns a string representing a single sequence (including gaps) from
+ * this alignment.
+ */
+ public String getAlignedSequenceString(int seq) {
+ char[] data = new char[numSites];
+ for (int i = 0; i < numSites; i++) {
+ data[i] = getData(seq, i);
+ }
+ return new String(data);
+ }
+
+ // seq names
+ public String getIdentifier(int i) {
+ return seqIDs.get(i);
+ }
+
+ public void setIdentifier(int i, String name) {
+ seqIDs.set(i,name);
+ }
+
+ public int getIdCount() {
+ return seqIDs.size();
+ }
+
+ public int whichIdNumber(String name) {
+ int j = 0;
+ for (int i = 0; i < seqIDs.size(); i++) {
+ if (seqIDs.get(i).compareTo(name) == 0)
+ j = i;
+ }
+ return j;
+ }
+
+}
diff --git a/src/jade/data/Alignment.java b/src/jade/data/Alignment.java
new file mode 100644
index 0000000..2ed8dcf
--- /dev/null
+++ b/src/jade/data/Alignment.java
@@ -0,0 +1,158 @@
+/*
+ * Alignment.java
+ *
+ * Created on April 18, 2005, 8:22 PM
+ */
+
+package jade.data;
+import java.util.*;
+/**
+ *
+ * @author stephensmith
+ *
+ * currently uses some of the PAL routines
+ */
+public class Alignment extends AbstractAlignment{
+
+ public Alignment() {}
+
+// public Alignment(AbstractAlignment a) {
+// this(a,(LabelMapping)null);
+// }
+
+// public Alignment(AbstractAlignment a, int sequenceToIgnore) {
+// int numberOfOriginalSequences = a.getIdCount();
+// setDataType(a.getDataType());
+// String[] sequences =
+// (
+// sequenceToIgnore<0||sequenceToIgnore>=numberOfOriginalSequences ?
+// new String[numberOfOriginalSequences] :
+// new String[numberOfOriginalSequences-1]
+// );
+// int index = 0;
+// for (int i = 0; i < numberOfOriginalSequences ; i++) {
+// if(i!=sequenceToIgnore) {
+// sequences[index++] = a.getAlignedSequenceString(i);
+// }
+// }
+//
+// init(new SimpleIdGroup(a,sequenceToIgnore), sequences, GAPS);
+// }
+
+ public Alignment(ArrayList<String> seqNames, ArrayList<String> sequences, String gaps, DataType dt) {
+ setDataType(dt);
+ init(seqNames, sequences, gaps);
+ }
+ public Alignment(ArrayList<String> seqNames, ArrayList<String> sequences, DataType dt) {
+ this(seqNames,sequences,null,dt);
+ }
+
+ private void init(ArrayList<String> seqNames, ArrayList<String> sequences, String gaps) {
+ sequences = getPadded(sequences);
+ numSeqs = sequences.size();
+ numSites = sequences.get(0).length();
+ seqIDs = seqNames;
+ this.sequences = sequences;
+ if (gaps != null) {
+ convertGaps(gaps);
+ }
+ }
+
+ /**
+ * Constructor taking single identifier and sequence.
+ */
+ public Alignment(String seqName, String sequence, DataType dataType) {
+
+ setDataType(dataType);
+ numSeqs = 1;
+ numSites = sequence.length();
+
+ sequences = new ArrayList<String>();
+ sequences.set(0,sequence);
+
+ seqIDs = new ArrayList<String>();
+ seqIDs.set(0, seqName);
+ }
+
+ /**
+ * This constructor combines to alignments given two guide strings.
+ */
+// public Alignment(AbstractAlignment a, AbstractAlignment b,
+// String guide1, String guide2, char gap) {
+//
+// sequences = new String[a.getSequenceCount() + b.getSequenceCount()];
+// numSeqs = sequences.length;
+//
+// for (int i = 0; i < a.getSequenceCount(); i++) {
+// sequences[i] = getAlignedString(a.getAlignedSequenceString(i), guide1, gap, GAP);
+// }
+// for (int i = 0; i < b.getSequenceCount(); i++) {
+// sequences[i + a.getSequenceCount()] =
+// getAlignedString(b.getAlignedSequenceString(i), guide2, gap, GAP);
+// }
+//
+// numSites = sequences[0].length();
+// idGroup = new SimpleIdGroup(a, b);
+// }
+//
+ /** sequence alignment at (sequence, site) */
+ public char getData(int seq, int site) {
+ return sequences.get(seq).charAt(site);
+ }
+
+ /**
+ * Returns a string representing a single sequence (including gaps)
+ * from this alignment.
+ */
+ public String getAlignedSequenceString(int seq) {
+ return sequences.get(seq);
+ }
+
+ // PRIVATE STUFF
+
+// private String getAlignedString(String original, String guide, char guideChar, char gapChar) {
+// StringBuffer buf = new StringBuffer(guide.length());
+// int seqcounter = 0;
+// for (int j = 0; j < guide.length(); j++) {
+// if (guide.charAt(j) != guideChar) {
+// buf.append(original.charAt(seqcounter));
+// seqcounter += 1;
+// } else {
+// buf.append(gapChar);
+// }
+// }
+// return new String(buf);
+// }
+ private static final String getPadded(String s, int length) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(s);
+ for(int i = s.length() ; i < length ; i++) {
+ sb.append(Alignment.GAP);
+ }
+ return sb.toString();
+ }
+ private static final ArrayList<String> getPadded(ArrayList<String> sequences) {
+ ArrayList<String> padded = new ArrayList<String>();
+ int maxLength = 0;
+ for(int i = 0 ; i < sequences.size() ; i++) {
+ maxLength = Math.max(maxLength,sequences.get(i).length());
+ }
+ for(int i = 0 ; i < sequences.size() ; i++) {
+ padded.add(getPadded(sequences.get(i),maxLength));
+ }
+ return padded;
+
+ }
+ /**
+ * Converts all gap characters to Alignment.GAP
+ */
+ private void convertGaps(String gaps) {
+ for (int i = 0; i < sequences.size(); i++) {
+ for (int j = 0; j < gaps.length(); j++) {
+ sequences.set(i,sequences.get(i).replace(gaps.charAt(j), Alignment.GAP));
+ }
+ }
+ }
+
+ ArrayList<String> sequences;
+}
diff --git a/src/jade/data/AlignmentReader.java b/src/jade/data/AlignmentReader.java
new file mode 100644
index 0000000..c041bdd
--- /dev/null
+++ b/src/jade/data/AlignmentReader.java
@@ -0,0 +1,483 @@
+package jade.data;
+
+import java.io.*;
+import java.util.*;
+
+public class AlignmentReader {
+ /** read from stream */
+ public AlignmentReader(PushbackReader input)throws IOException {
+ readFile(input);
+ }
+
+ /** read from file */
+ public AlignmentReader(String file)throws IOException {
+ PushbackReader input = openFile(file);
+ readFile(input);
+ input.close();
+ }
+
+ public Alignment getAlignment(){
+ guessDataType();
+ DataType dt = dataType;
+ FinalAlignment = new Alignment(seqIDs,sequences, dt);
+ return FinalAlignment;
+ }
+ private void guessDataType() {
+ // count A, C, G, T, U, N
+ long numNucs = 0;
+ long numChars = 0;
+ long numBins = 0;
+ for (int i = 0; i < numSeqs; i++) {
+ for (int j = 0; j < numSites; j++) {
+ char c = getData(i, j);
+
+ if (c == 'A' || c == 'C' || c == 'G' ||
+ c == 'T' || c == 'U' || c == 'N') numNucs++;
+
+ if (c != '-' && c != '?') numChars++;
+
+ if (c == '0' || c == '1') numBins++;
+ }
+ }
+
+ if (numChars == 0) numChars = 1;
+
+ // more than 85 % frequency advocates nucleotide data
+ if ((double) numNucs / (double) numChars > 0.85) {
+ dataType = new NucleotideDataType();
+ } else if ((double) numBins / (double) numChars > 0.2) {
+ dataType = new BinaryDataType();
+ }
+ }
+ // Implementation of abstract Alignment method
+
+ /** sequence alignment at (sequence, site) */
+ public char getData(int seq, int site) {
+ return data[seq][site];
+ }
+ /** number of sequences */
+ protected int numSeqs;
+
+ /** length of each sequence */
+ protected int numSites;
+
+ /** sequence identifiers */
+ protected ArrayList<String> seqIDs;
+ protected ArrayList<String> sequences;
+
+ /** data type */
+ private DataType dataType;
+
+ private int lineLength;
+ //private Vector names, seqs, sites;
+
+ //Alignment to send back in the getAlignment method
+ private Alignment FinalAlignment;
+
+ // Raw sequence alignment [sequence][site]
+ private char[][] data = null;
+
+// private final boolean isType(PushbackReader in, String id) throws IOException {
+//
+// for (int i = 0; i < id.length(); i++) {
+// int c = readNextChar(in);
+// if (c != id.charAt(i) ) {
+// in.unread(c);
+// return false;
+// }
+// }
+// return true;
+// }
+
+ private void readFile(PushbackReader in) throws IOException {
+ readPHYLIP(in);
+
+ // Capitalize
+ for (int i = 0; i < numSeqs; i++){
+ for (int j = 0; j < numSites; j++){
+ data[i][j] = Character.toUpperCase(data[i][j]);
+ }
+ }
+ // Estimate data type
+ //guessDataType();
+ processSeqs();
+ }
+ private void processSeqs(){
+ sequences = new ArrayList<String>();
+ for(int i=0;i<numSeqs;i++){
+ sequences.add("");
+ }
+ for(int i=0;i<numSeqs;i++){
+ for(int j=0;j<numSites;j++){
+ sequences.set(i, sequences.get(i)+data[i][j]);
+ }
+ }
+ }
+ private void readPHYLIP(PushbackReader in){
+
+ int c, pos = 0, seq = 0;
+
+ try {
+ // Parse PHYLIP header line
+ numSeqs = readInt(in);
+ numSites = readInt(in);
+
+ // Reserve memory
+ seqIDs = new ArrayList<String>();
+ for (seq = 0; seq < numSeqs; seq++) {seqIDs.add("");};
+ data = new char[numSeqs][numSites];
+
+
+ // Determine whether sequences are in INTERLEAVED
+ // or in sequential format
+ String header = readLine(in, false);
+
+ boolean interleaved = true;
+
+ if (header.length() > 0) {
+ if (header.charAt(0) == 'S') {
+ interleaved = false;
+ }
+ }
+
+ if (interleaved) // PHYLIP INTERLEAVED
+ {
+ //System.out.println("PHYLIP INTERLEAVED");
+
+
+ // Reading data
+ while (pos < numSites) {
+ // Go to next block
+ c = readNextChar(in);
+ in.unread(c);
+
+ for (seq = 0; seq < numSeqs; seq++) {readSeqLineP(in, seq, pos, numSites);
+ }
+ pos += lineLength;
+ }
+ } else // PHYLIP SEQUENTIAL
+ {
+ //System.out.println("PHYLIP SEQUENTIAL");
+
+ for (seq = 0; seq < numSeqs; seq++) {
+ // Go to next block
+ c = readNextChar(in);
+ in.unread(c);
+
+ // Read label
+ seqIDs.set(seq,readLabel(in, 10));
+
+ // Read sequences
+ for (pos = 0; pos < numSites; pos++) {
+ data[seq][pos] = (char) readNextChar(in);
+
+ if (data[0][pos] == '.') {
+ if (seq == 0) {
+ } else {
+ data[seq][pos] = data[0][pos];
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {}
+ }
+
+ private void readSeqLineP(PushbackReader in, int s, int pos, int maxPos) throws IOException {
+
+ if (pos == 0) {
+ seqIDs.set(s,readLabel(in, 10));
+ }
+
+ if (s == 0) {
+ String thisLine = readLine(in, false);
+
+ if (thisLine.length() > maxPos - pos) {
+ lineLength = maxPos - pos;
+ } else {
+ lineLength = thisLine.length();
+ }
+
+ for (int i = 0; i < lineLength; i++) {
+ data[0][pos + i] = thisLine.charAt(i);
+ if (data[0][pos + i] == '.') {
+
+ }
+ }
+ } else {
+ for (int i = 0; i < lineLength; i++) {
+ data[s][pos + i] = (char) readNextChar(in);
+ if (data[s][pos + i] == '.') {
+ data[s][pos + i] = data[0][pos + i];
+ }
+ }
+ nextLine(in);
+ }
+ }
+ private PushbackReader openFile(String name) throws FileNotFoundException {
+ return new PushbackReader(new BufferedReader(new FileReader(name)));
+ }
+ private static boolean isWhite(int c) {
+ return Character.isWhitespace((char) c);
+ }
+
+ private static boolean isNewlineCR(int c) {
+ if (c == '\n' || c == 'r') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ /**
+ * go to the beginning of the next line.
+ * Recognized line terminators:
+ * Unix: \n, DOS: \r\n, Macintosh: \r
+ *
+ * @param in input stream
+ */
+ public void nextLine(PushbackReader in)throws IOException {
+ readLine(in, false);
+ }
+
+ /**
+ * read a whole line
+ *
+ * @param in input stream
+ * @param keepWhiteSpace keep or drop white space
+ *
+ * @return string with content of line
+ */
+ public String readLine(PushbackReader in, boolean keepWhiteSpace)throws IOException {
+ StringBuffer buffer = new StringBuffer();
+
+ int EOF = -1;
+ int c;
+
+ c = in.read();
+ while (c != EOF && c != '\n' && c != '\r') {
+ if (!isWhite(c) || keepWhiteSpace) {
+ buffer.append((char) c);
+ }
+ c = in.read();
+ }
+
+ if (c == '\r') {
+ c = in.read();
+ if (c != '\n') {
+ in.unread(c);
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * go to first non-whitespace character
+ *
+ * @param in input stream
+ *
+ * @return character or EOF
+ */
+ public int skipWhiteSpace(PushbackReader in) throws IOException {
+ int EOF = -1;
+ int c;
+
+ // search for first non-whitespace character
+ do
+ {
+ c = in.read();
+ }
+ while (c != EOF && isWhite(c));
+
+ return c;
+ }
+
+ /**
+ * read next character from stream
+ * (EOF does not count as character but will throw exception)
+ *
+ * @param input input stream
+ *
+ * @return character
+ */
+ public int readNextChar(PushbackReader input) throws IOException {
+ int EOF = -1;
+
+ int c = skipWhiteSpace(input);
+
+ if (c == EOF) {
+ new IOException("End of file/stream");
+ }
+ return c;
+ }
+
+ /**
+ * read word from stream
+ *
+ * @param input stream
+ *
+ * @return word read from stream
+ */
+ public String readWord(PushbackReader in) throws IOException {
+ StringBuffer buffer = new StringBuffer();
+
+ int EOF = -1;
+ int c;
+
+ c = skipWhiteSpace(in);
+
+ // search for last non-whitespace character
+ while (c != EOF && !isWhite(c)) {
+ buffer.append((char) c);
+ c = in.read();
+ }
+
+ if (c != EOF) {
+ in.unread(c);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * read sequence label from stream
+ *
+ * A sequence label is not allowed to contain
+ * whitespace and either of :,;()[]{}. Note
+ * that newline/cr is NOT counted as white space!!
+ *
+ * @param in input stream
+ * @param maxLength maximum allowed length of label
+ * (if negative any length is permitted)
+ *
+ * @return label
+ */
+ public String readLabel(PushbackReader in, int maxLength)throws IOException {
+ StringBuffer buffer = new StringBuffer();
+
+ int EOF = -1;
+ int c;
+ //int len = 0;
+
+
+ c = skipWhiteSpace(in);
+
+ // search for last label character
+ //while (c != EOF && buffer.length() != maxLength &&
+ while (c != EOF &&
+ !(
+ (isWhite(c) && c != '\n' && c != '\r') ||
+ c == ':' || c == ',' || c == ';' ||
+ c == '(' || c == ')' ||
+ c == '[' || c == ']' ||
+ c == '{' || c == '}')) {
+ // read over newline/cr
+ if (c != '\n' && c != '\r') buffer.append((char) c);
+ c = in.read();
+ }
+
+ if (c != EOF) {
+ in.unread(c);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * read next number from stream
+ *
+ * @param in input stream
+ * @param ignoreNewlineCR ignore newline/cr as separator
+ *
+ * @return number (as string)
+ **/
+ public String readNumber(PushbackReader in, boolean ignoreNewlineCR) throws IOException {
+ StringBuffer buffer = new StringBuffer();
+
+ int EOF = -1;
+ int c;
+
+ // search for first number character
+ do
+ {
+ c = in.read();
+ }
+ while (c != EOF &&
+ !(c == '-' || c == '.' || Character.isDigit((char) c)));
+
+ // search for last number character
+ while (c != EOF &&
+ (c == '-' || c == '.' || c == 'e'
+ || c == 'E' || Character.isDigit((char) c))
+ || (isNewlineCR(c) && ignoreNewlineCR) ) {
+ if (!(isNewlineCR(c) && ignoreNewlineCR))
+ buffer.append((char) c);
+ c = in.read();
+ }
+
+ if (c != EOF) {
+ in.unread(c);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * read next number from stream and convert it to a double
+ * (newline/cr are treated as separators)
+ *
+ * @param in input stream
+ *
+ * @return double
+ */
+ public double readDouble(PushbackReader in)throws IOException, NumberFormatException {
+ return readDouble(in, false);
+ }
+
+ /**
+ * read next number from stream and convert it to a double
+ *
+ * @param in input stream
+ * @param ignoreNewlineCR ignore newline/cr as separator
+ *
+ * @return double
+ */
+ public double readDouble(PushbackReader in, boolean ignoreNewlineCR) throws IOException, NumberFormatException {
+ String w = readNumber(in, ignoreNewlineCR);
+ if (w.length() == 0) {
+ throw new IOException("End of file/stream");
+ }
+
+ return Double.valueOf(w).doubleValue();
+ }
+
+
+ /**
+ * read next number from stream and convert it to a int
+ * (newline/cr are treated as separators)
+ *
+ * @param in input stream
+ *
+ * @return integer
+ */
+ public int readInt(PushbackReader in) throws IOException, NumberFormatException {
+ return readInt(in, false);
+ }
+
+ /**
+ * read next number from stream and convert it to a int
+ *
+ * @param in input stream
+ * @param ignoreNewlineCR ignore newline/cr as separator
+ *
+ * @return integer
+ */
+ public int readInt(PushbackReader in, boolean ignoreNewlineCR) throws IOException, NumberFormatException {
+ String w = readNumber(in, ignoreNewlineCR);
+ if (w.length() == 0) {
+ throw new IOException("End of file/stream");
+ }
+
+ return Integer.valueOf(w).intValue();
+ }
+}
\ No newline at end of file
diff --git a/src/jade/data/BinaryDataType.java b/src/jade/data/BinaryDataType.java
new file mode 100644
index 0000000..0c4e7c3
--- /dev/null
+++ b/src/jade/data/BinaryDataType.java
@@ -0,0 +1,131 @@
+/*
+ * BinaryDataType.java
+ *
+ * Created on April 18, 2005, 8:26 PM
+ */
+
+package jade.data;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class BinaryDataType implements DataType{
+
+ /** Creates a new instance of BinaryDataType */
+ public BinaryDataType() {}
+ public static final BinaryDataType DEFAULT_INSTANCE = new BinaryDataType();
+
+ public int getNumStates() {return 2;}
+
+ // Get state corresponding to character c
+ public int getStateCorr(char c) {
+ switch (c) {
+ case '0':
+ return 0;
+ case '1':
+ return 1;
+
+ case UNKNOWN_CHARACTER:
+ return 2;
+
+ default:
+ return 2;
+ }
+ }
+
+
+ protected final boolean isUnknownStateCorr(final int state) {
+ return(state>=2);
+ }
+
+ // Get character corresponding to a given state
+ protected char getCharCorr(final int state) {
+ switch (state) {
+ case 0:
+ return '0';
+ case 1:
+ return '1';
+
+ case 2:
+ return UNKNOWN_CHARACTER;
+
+ default:
+ return UNKNOWN_CHARACTER;
+ }
+ }
+
+ // String describing the data type
+ public String getDescription() {
+ return BINARY_DESCRIPTION;
+ }
+
+ // Get numerical code describing the data type
+ public int getTypeID() {
+ return 2;
+ }
+ /**
+ * Handles gap char and then passes on to getStateImpl
+ */
+ public final int getState(char c) {
+ if(isSuggestedGap(c)) { return SUGGESTED_GAP_STATE; }
+ return getStateCorr(c);
+ }
+ /**
+ * Handles gap state and then passes on to getStateImpl
+ */
+ public final char getChar(final int state) {
+ if(state==SUGGESTED_GAP_STATE) { return DEFAULT_GAP_CHARACTER; }
+ if(state<0) { return UNKNOWN_CHARACTER; }
+ return getCharCorr(state);
+ }
+
+ public final char getSuggestedChar(final char c) {
+ if(isGapChar(c)) {
+ return DEFAULT_GAP_CHARACTER;
+ }
+ return getPreferredCharImpl(c);
+ }
+
+ protected char getPreferredCharImpl(final char c) {
+ return getChar(getState(c));
+ }
+
+ public final boolean isUnknownChar(final char c) {
+ return isUnknownState(getState(c));
+ }
+
+ public final boolean isUnknownState(final int state) {
+ return(state==SUGGESTED_GAP_STATE||isUnknownStateCorr(state));
+ }
+
+ public String toString() { return getDescription(); }
+
+ public int getSuggestedUnknownState() { return SUGGESTED_UNKNOWN_STATE; }
+
+ public final boolean hasGapCharacter() { return true; }
+ /**
+ * @return true if this character is a '.' or a '_'
+ */
+ public final boolean isGapChar(final char c) {
+ return isSuggestedGap(c);
+ }
+
+ /**
+ * @return true if state is gap state (-2), false other wise
+ */
+ public final boolean isGapState(final int state) { return state==SUGGESTED_GAP_STATE; }
+
+ /**
+ * @return GAP_STATE (-2)
+ */
+ public final int getSuggestedGapState() { return SUGGESTED_GAP_STATE; }
+ //method used above
+ public static final boolean isSuggestedGap(char c) {
+ for(int i = 0 ; i < SUGGESTED_GAP_CHARACTERS.length ; i++) {
+ if(c==SUGGESTED_GAP_CHARACTERS[i]) { return true; }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/jade/data/ConcatenateAlignment.java b/src/jade/data/ConcatenateAlignment.java
new file mode 100644
index 0000000..96e6dba
--- /dev/null
+++ b/src/jade/data/ConcatenateAlignment.java
@@ -0,0 +1,148 @@
+package jade.data;
+
+import java.util.*;
+import java.io.*;
+
+/*
+ * this takes fasta files and will concatenate them
+ *
+ * the algorithm works as such
+ * 1 take each file (assumes that you used muscle and each gene is in each file)
+ * 2 store the names from each file for a union
+ */
+
+public class ConcatenateAlignment {
+ public ConcatenateAlignment(ArrayList <String> fastaFiles, boolean verbose, String outfile){
+ this.fastaFiles = fastaFiles;
+ finalNames = new ArrayList <String>();
+ geneLengths = new ArrayList<Integer>();
+ allSeqs = new ArrayList <ArrayList<Sequence>>();
+ finalSeqs = new ArrayList <Sequence> ();
+ run();
+ System.out.println("#NEXUS");
+ printtofile(outfile);
+ if(verbose == true){
+ System.out.println("[total number of taxa: "+finalNames.size());
+ System.out.println("taxa names");
+ System.out.println("-----------");
+ for(int i=0;i<finalSeqs.size();i++){
+ System.out.println(finalSeqs.get(i).getID());
+ }
+ System.out.println("]");
+ }
+ }
+
+ private void run(){
+ /*
+ * this should union the taxa names
+ * and make sure that genelengths are correct
+ */
+ for(int i=0;i<fastaFiles.size();i++){
+ try{
+ FastaReader fr = new FastaReader((String)fastaFiles.get(i));
+ ArrayList<Sequence> seqs = fr.getSeqs();
+ allSeqs.add(seqs);
+ union(seqs);
+ if(testSeqLengths(seqs)==true){
+ geneLengths.add(seqs.get(0).getSeq().length());
+ }else{
+ System.err.println("you have different lengths in file number "+(i+1));
+ }
+ }catch(IOException ioe){
+ System.out.println("there was a problem reading one of your files");
+ }
+ }
+ /*
+ * this creates a new sequence (the final one) from the union which will be used to make the concatenated sequences
+ */
+ for(int i=0;i<finalNames.size();i++){
+ Sequence seq = new Sequence(finalNames.get(i),"");
+ finalSeqs.add(seq);
+ }
+ /*
+ * this should make the strings for each taxa that will be used
+ * for nexus file
+ */
+ for(int i=0;i<allSeqs.size();i++){
+ for(int j=0;j<finalSeqs.size();j++){
+ boolean here = false;
+ for(int k=0;k<allSeqs.get(i).size();k++){
+ if(finalSeqs.get(j).getID().compareTo(allSeqs.get(i).get(k).getID())==0){
+ finalSeqs.get(j).seqSeq(finalSeqs.get(j).getSeq()+allSeqs.get(i).get(k).getSeq());
+ here = true;
+ }
+ }
+ if(here == false){
+ for(int k = 0;k<geneLengths.get(i);k++){
+ finalSeqs.get(j).seqSeq(finalSeqs.get(j).getSeq()+"-");
+ }
+ }
+ }
+ }
+ }
+
+ private void union(ArrayList<Sequence> seqs){
+ if(finalNames.size()<1){
+ for(int i=0;i< seqs.size();i++){
+ finalNames.add(((Sequence)seqs.get(i)).getID().trim());
+ }
+ }else{
+ for(int i=0;i<seqs.size();i++){
+ boolean b = false;
+ for(int j = 0;j<finalNames.size();j++){
+ if(((Sequence)seqs.get(i)).getID().trim().compareTo(finalNames.get(j))==0){
+ b = true;
+ }
+ }
+ if(b == false){
+ finalNames.add(((Sequence)seqs.get(i)).getID().trim());
+ }
+ }
+ }
+ }
+
+ private boolean testSeqLengths(ArrayList<Sequence> seqs){
+ boolean ret = true;
+ int j = seqs.get(0).getSeq().length();
+ for(int i=1;i<seqs.size();i++){
+ if(seqs.get(i).getSeq().length()!=j)
+ ret = false;
+ }
+ return ret;
+ }
+
+
+ private void printtofile(String outfile){
+ //System.out.println("#NEXUS");
+ System.out.println("BEGIN DATA;");
+ System.out.print("\t[");
+ int cur = 1;
+ for(int i=0;i<geneLengths.size();i++){
+ System.out.print("gene"+(i+1)+" "+cur+"-"+(cur+geneLengths.get(i)-1)+" ");
+ cur += geneLengths.get(i);
+ }System.out.print("]\n");
+ System.out.println("\tDIMENSIONS NTAX="+finalSeqs.size()+" NCHAR="+finalSeqs.get(0).getSeq().length()+";");
+ System.out.println("\tFORMAT DATATYPE = DNA GAP = - MISSING = ?;");
+ System.out.println("\tMATRIX");
+ for(int i=0;i<finalSeqs.size();i++){
+ System.out.println("\t"+finalSeqs.get(i).getID()+"\t"+finalSeqs.get(i).getSeq());
+ }
+ System.out.println(";");
+ System.out.println("END;");
+ System.out.println();
+ }
+
+ public static void main (String [] args){
+ ArrayList<String> ff = new ArrayList<String>();
+ ff.add("/home/smitty/programming/working_copy/PhyJ/stuff/test.fasta");
+ ff.add("/home/smitty/programming/working_copy/PhyJ/stuff/test2.fasta");
+ new ConcatenateAlignment(ff, false,"/home/smitty/programming/working_copy/PhyJ/stuff/test.out");
+ }
+ private ArrayList <String> fastaFiles;
+
+ private ArrayList <String> finalNames;
+ private ArrayList <ArrayList<Sequence>> allSeqs;
+ private ArrayList <Sequence> finalSeqs;
+ private ArrayList <Integer> geneLengths;
+ private String outfile;
+}
diff --git a/src/jade/data/DataType.java b/src/jade/data/DataType.java
new file mode 100644
index 0000000..37471d4
--- /dev/null
+++ b/src/jade/data/DataType.java
@@ -0,0 +1,58 @@
+package jade.data;
+
+public interface DataType {
+ char UNKNOWN_CHARACTER = '?';
+
+ char DEFAULT_GAP_CHARACTER = '-';
+
+ /*
+ * types of data
+ */
+ int BINARYDATATYPE = 0;
+
+ int NUCLEOTIDEDATATYPE = 1;
+
+ int NUMERICDATATYPE = 2;
+
+ int CONTDATATYPE = 3;
+
+ int UNKNOWN = 666;
+
+ char[] SUGGESTED_GAP_CHARACTERS = { DEFAULT_GAP_CHARACTER, '_', '.' };
+
+ int SUGGESTED_GAP_STATE = -2;
+
+ int SUGGESTED_UNKNOWN_STATE = -1;
+
+ String NUCLEOTIDE_DESCRIPTION = "nucleotide";
+
+ String BINARY_DESCRIPTION = "binary";
+
+ String GAP_BALANCED_DESCRIPTION = "gap balanced";
+
+ String getDescription();
+
+ int getState(char c);
+
+ char getChar(int state);
+
+ int getNumStates();
+
+ char getSuggestedChar(char c);
+
+ int getTypeID();
+
+ boolean isUnknownState(int state);
+
+ boolean isUnknownChar(char c);
+
+ boolean hasGapCharacter();
+
+ boolean isGapChar(char c);
+
+ boolean isGapState(int state);
+
+ int getSuggestedGapState();
+
+ int getSuggestedUnknownState();
+}
diff --git a/src/jade/data/FastaReader.java b/src/jade/data/FastaReader.java
new file mode 100644
index 0000000..73cdc3a
--- /dev/null
+++ b/src/jade/data/FastaReader.java
@@ -0,0 +1,60 @@
+package jade.data;
+
+import java.io.*;
+import java.util.*;
+
+public class FastaReader {
+ public FastaReader(String filename)throws IOException{
+ seqs = new ArrayList<Sequence>();
+ input = openFile(filename);
+ read();
+ //System.out.println(seqs.size());
+ input.close();
+ }
+ public FastaReader(File infile)throws IOException{
+ seqs = new ArrayList<Sequence>();
+ input = new BufferedReader(new FileReader(infile));
+ read();
+ System.out.println(seqs.size());
+ input.close();
+ }
+ public ArrayList<Sequence> getSeqs(){return seqs;}
+ private BufferedReader openFile(String file) throws FileNotFoundException{
+ return new BufferedReader(new FileReader(file));
+ }
+ private void read(){
+ try{
+ String str = "";
+ String seqstr = "";
+ Sequence curseq = new Sequence();
+ while((str=input.readLine())!=null){
+ if(str.startsWith(">")){
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqstr = "";
+ seqs.add(curseq);
+ }
+ curseq = new Sequence();
+ str = str.substring(1).trim();
+ curseq.setID(str);
+ }else{
+ seqstr += str.trim();
+ }
+ }
+ //this would be for the last seq
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqs.add(curseq);
+ }
+ }catch(IOException ioe){
+ System.out.println("there was a problem reading your file (FastaReader.java)");
+ }
+ }
+ public static void main(String [] args){
+ try{
+ new FastaReader("/home/smitty/programming/working_copy/PhyJ/stuff/test.fasta");
+ }catch(IOException ioe){}
+ }
+ private ArrayList<Sequence> seqs;
+ private BufferedReader input;
+}
diff --git a/src/jade/data/GBParser.java b/src/jade/data/GBParser.java
new file mode 100644
index 0000000..6d3866a
--- /dev/null
+++ b/src/jade/data/GBParser.java
@@ -0,0 +1,283 @@
+package jade.data;
+/*
+ * This should parse a gb fasta file with 1 or more fasta sequences
+ * The idea is to use this to prepare the file for use with concat after muscle
+ */
+
+import java.util.*;
+import java.io.*;
+import java.util.regex.*;
+
+public class GBParser{
+ /*
+ * after the filename this requires an int representing
+ * 1 = gi
+ * 2 = gb (with decimal)
+ * 3 = taxon
+ * 4 = taxon with first name_last name
+ * 5 = taxon with one letter from first name uppercase_last name
+ * 6 = taxon with first name_last name and gb (with decimal)
+ */
+ public GBParser(String filename, int num) throws IOException{
+ this.num = num;
+ seqs = new ArrayList<Sequence>();
+ same = new HashMap<String,Integer>();
+ input = openFile(filename);
+ read();
+ input.close();
+ //for(int i =0 ;i <seqs.size();i++){
+ // System.out.println(seqs.get(i).getID());
+ //}
+ }
+
+ public GBParser(String filename, String region) throws IOException{//for bioorganizer
+ this.region = region;
+ seqs = new ArrayList<Sequence>();
+ same = new HashMap<String,Integer>();
+ input = openFile(filename);
+ readBioorganizer();
+ input.close();
+ }
+
+ /*
+ * use just for bioorganizer
+ */
+ private void readBioorganizer(){
+ try{
+ String str = "";
+ String seqstr = "";
+ Sequence curseq = new Sequence();
+ while((str=input.readLine())!=null){
+ if(str.startsWith(">")){
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqstr = "";
+ seqs.add(curseq);
+ }
+ curseq = new Sequence();
+ str = str.substring(1).trim();
+ /*
+ * this is where I edit the genbank line given the int
+ */
+ str=str.replace('|','\'');
+ String [] astr = str.split("\'");
+ String gi = astr[1];
+ str = astr[4].trim();
+ astr = str.split(" ");
+ if(badForm(astr[0]) == true)
+ str = astr[1]+"_"+astr[2]+"_"+region+"__"+gi;
+ else
+ str = astr[0]+"_"+astr[1]+"_"+region+"__"+gi;
+ /*
+ * this is for checking for doubles
+ */
+ boolean b = false;
+ int in = 1;
+ for(int i=0;i<seqs.size();i++){
+ if(str.compareTo(seqs.get(i).getID())==0){
+ if(b==false){
+ b=true;
+ in++;
+ same.put(str,in);
+ }
+ }else if(same.containsKey(str)){
+ in = same.get(str);
+ in++;
+ same.put(str,in);
+ }
+ }
+ if(b == false)
+ curseq.setID(str);
+ else
+ curseq.setID(str+String.valueOf(in));
+ }else{
+ seqstr += str.trim();
+ }
+ }
+ //this would be for the last seq
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqs.add(curseq);
+ }
+ }catch(IOException ioe){
+ System.out.println("there was a problem reading your file (GBParser.java)");
+ }
+ }
+
+ private void read(){
+ try{
+ String str = "";
+ String seqstr = "";
+ Sequence curseq = new Sequence();
+ while((str=input.readLine())!=null){
+ if(str.startsWith(">")){
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqstr = "";
+ seqs.add(curseq);
+ }
+ curseq = new Sequence();
+ str = str.substring(1).trim();
+ /*
+ * this is where I edit the genbank line given the int
+ */
+ str=str.replace('|','\'');
+ if(num == 1){//gi
+ String [] astr = str.split("\'");
+ str = astr[1];
+ }else if(num == 2){//gb
+ String [] astr = str.split("\'");
+ str = astr[3];
+ }else if(num == 3){//taxon
+ String [] astr = str.split("\'");
+ str = astr[4].trim();
+ astr = str.split(" ");
+ int st = 0;
+ while(allCaps(astr[st]) == true){
+ st++;
+ }
+ str = astr[st]+" "+astr[st+1];
+ /*
+ if(badForm(astr[0]) == true)
+ str = astr[1]+" "+astr[2];
+ else
+ str = astr[0]+" "+astr[1];
+ */
+ }else if(num == 4){//taxon with name_name
+ String [] astr = str.split("\'");
+ str = astr[4].trim();
+ astr = str.split(" ");
+ int st = 0;
+ while(allCaps(astr[st]) == true){
+ st++;
+ }
+ str = astr[st]+"_"+astr[st+1];
+ /*
+ if(badForm(astr[0]) == true)
+ str = astr[1]+"_"+astr[2];
+ else
+ str = astr[0]+"_"+astr[1];
+ */
+ }else if(num == 5){//taxon with n_name
+ String [] astr = str.split("\'");
+ str = astr[4].trim();
+ astr = str.split(" ");
+ int st = 0;
+ while(allCaps(astr[st]) == true){
+ st++;
+ }
+ str = astr[st].substring(0,1)+"_"+astr[st+1];
+ /*
+ if(badForm(astr[0]) == true)
+ str = astr[1].substring(0,1)+"_"+astr[2];
+ else
+ str = astr[0].substring(0,1)+"_"+astr[1];
+ */
+ }else if(num == 6){
+ String [] astr = str.split("\'");
+ String gi = astr[1];
+ str = astr[4].trim();
+ astr = str.split(" ");
+ int st = 0;
+ while(allCaps(astr[st]) == true){
+ st++;
+ }
+ str = astr[st]+"_"+astr[st+1]+"_"+gi;
+ /*
+ if(badForm(astr[0]) == true)
+ str = astr[1]+"_"+astr[2]+"_"+gi;
+ else
+ str = astr[0]+"_"+astr[1]+"_"+gi;
+ */
+ }else if(num == 7){
+ String [] astr = str.split("\'");
+ String gb = astr[3];
+ str = astr[4].trim();
+ astr = str.split(" ");
+ int st = 0;
+ while(allCaps(astr[st]) == true){
+ st++;
+ }
+ str = astr[st]+"_"+astr[st+1]+"_"+gb;
+ /*
+ if(badForm(astr[0]) == true)
+ str = astr[1]+"_"+astr[2]+"_"+gb;
+ else
+ str = astr[0]+"_"+astr[1]+"_"+gb;
+ */
+ }
+ /*
+ * this is for checking for doubles
+ */
+ boolean b = false;
+ int in = 1;
+ for(int i=0;i<seqs.size();i++){
+ if(str.compareTo(seqs.get(i).getID())==0){
+ if(b==false){
+ b=true;
+ in++;
+ same.put(str,in);
+ }
+ }else if(same.containsKey(str)){
+ in = same.get(str);
+ in++;
+ same.put(str,in);
+ }
+ }
+ if(b == false)
+ curseq.setID(str);
+ else
+ curseq.setID(str+String.valueOf(in));
+ }else{
+ seqstr += str.trim();
+ }
+ }
+ //this would be for the last seq
+ if(seqstr.compareTo("")!=0&&curseq!=null){
+ curseq.seqSeq(seqstr);
+ seqs.add(curseq);
+ }
+ }catch(IOException ioe){
+ System.out.println("there was a problem reading your file (GBParser.java)");
+ }
+ }
+ public ArrayList<Sequence> getSeqs(){return seqs;}
+ private BufferedReader openFile(String file) throws FileNotFoundException{
+ return new BufferedReader(new FileReader(file));
+ }
+ public static void main (String [] args){
+ try{
+ new GBParser("/home/smitty/programming/working_copy/gbparser/stuff/test.fasta",3);
+ }catch(IOException ioe){}
+ }
+ //private
+ private ArrayList<Sequence> seqs;
+ private BufferedReader input;
+ private int num;
+ private HashMap<String,Integer> same;
+ private String region;
+ /*
+ * test for malformed genbank taxon form
+ */
+ private boolean badForm(String ins){
+ boolean ret = false;
+ Pattern pattern = Pattern.compile("\\d");
+ Matcher matcher = pattern.matcher(ins);
+ boolean found = false;
+ while (matcher.find()) {
+ found = true;
+ }
+ if(found == true)
+ ret = true;
+ return ret;
+ }
+ private boolean allCaps(String ins){
+ boolean ret = true;
+ for(int i=0;i<ins.length();i++){
+ if(Character.isLowerCase(ins.charAt(i))){
+ ret = false;
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/jade/data/NucleotideDataType.java b/src/jade/data/NucleotideDataType.java
new file mode 100644
index 0000000..8874173
--- /dev/null
+++ b/src/jade/data/NucleotideDataType.java
@@ -0,0 +1,297 @@
+/*
+ * NucleotideDataType.java
+ *
+ * Created on April 18, 2005, 8:26 PM
+ */
+
+package jade.data;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class NucleotideDataType implements DataType{
+
+ public static final int A_STATE = 0;
+ public static final int C_STATE = 1;
+ public static final int G_STATE = 2;
+ public static final int UT_STATE = 3;
+ public final int getSuggestedGapState() { return SUGGESTED_GAP_STATE; }
+ public final boolean isGapState(final int state) { return state==SUGGESTED_GAP_STATE; }
+ //
+ // Variables
+ //
+
+ private static final char[] DNA_CONVERSION_TABLE = {'A', 'C', 'G', 'T', UNKNOWN_CHARACTER};
+ private static final char[] RNA_CONVERSION_TABLE = {'A', 'C', 'G', 'T', UNKNOWN_CHARACTER};
+
+ //For faster conversion!
+ boolean isRNA_;
+ char[] conversionTable_;
+
+ //Must stay after static CONVERSION_TABLE stuff!
+ public static final NucleotideDataType DEFAULT_INSTANCE = new NucleotideDataType();
+
+ public NucleotideDataType() {
+ this(false);
+ }
+
+ /** If isRNA is true than getChar(state) will return a U instead of a T */
+ public NucleotideDataType(boolean isRNA) {
+ this.isRNA_ = isRNA;
+ conversionTable_ = (isRNA_ ? RNA_CONVERSION_TABLE : DNA_CONVERSION_TABLE );
+ }
+
+ // Get number of bases
+ public int getNumStates() {
+ return 4;
+ }
+
+ /**
+ * @return true if this state is an unknown state
+ */
+ protected final boolean isUnknownStateImpl(final int state) {
+ return(state>=4)||(state<0);
+ }
+
+ /**
+ * Get state corresponding to character c <BR>
+ * <B>NOTE</B>: IF YOU CHANGE THIS IT MAY STOP THE NUCLEOTIDE TRANSLATOR FROM WORKING!
+ * - It relies on the fact that all the states for 'ACGTU' are between [0, 3]
+ */
+ protected int getStateImpl(char c) {
+ switch (c) {
+ case 'A':
+ return A_STATE;
+ case 'C':
+ return C_STATE;
+ case 'G':
+ return G_STATE;
+ case 'T':
+ return UT_STATE;
+ case 'U':
+ return UT_STATE;
+ case UNKNOWN_CHARACTER:
+ return 4;
+ case 'a':
+ return A_STATE;
+ case 'c':
+ return C_STATE;
+ case 'g':
+ return G_STATE;
+ case 't':
+ return UT_STATE;
+ case 'u':
+ return UT_STATE;
+ default:
+ return 4;
+ }
+ }
+
+ /**
+ * Get character corresponding to a given state
+ */
+ protected char getCharImpl(final int state) {
+ if(state<conversionTable_.length&&state>=0){
+ return conversionTable_[state];
+ }
+ return UNKNOWN_CHARACTER;
+ }
+
+ /**
+ * @return a string describing the data type
+ */
+ public String getDescription() {
+ return NUCLEOTIDE_DESCRIPTION;
+ }
+
+ /**
+ * @return the unique numerical code describing the data type
+ */
+ public int getTypeID() {
+ return 0;
+ }
+
+ /**
+ * @return true if A->G, G->A, C->T, or T->C
+ * if firstState equals secondState returns FALSE!
+ */
+ public final boolean isTransitionByState(int firstState, int secondState) {
+ switch(firstState) {
+ case A_STATE: {
+ if(secondState==G_STATE) {
+ return true;
+ }
+ return false;
+ }
+ case C_STATE : {
+ if(secondState==UT_STATE) {
+ return true;
+ }
+ return false;
+ }
+ case G_STATE : {
+ if(secondState==A_STATE) {
+ return true;
+ }
+ return false;
+ }
+ case UT_STATE : {
+ if(secondState==C_STATE) {
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return true if A->G, G->A, C->T, or T->C
+ * if firstState equals secondState returns FALSE!
+ * (I've renamed things to avoid confusion between java typing of ints and chars)
+ */
+ public final boolean isTransitionByChar(char firstChar, char secondChar) {
+ //I'm leaving it open to a possible optimisation if anyone cares.
+ return isTransitionByState(getState(firstChar), getState(secondChar));
+ }
+ public final int getState(char c) {
+ if(isSuggestedGap(c)) { return SUGGESTED_GAP_STATE; }
+ return getStateImpl(c);
+ }
+ /**
+ * Handles gap state and then passes on to getStateImpl
+ */
+ public final char getChar(final int state) {
+ if(state==SUGGESTED_GAP_STATE) { return DEFAULT_GAP_CHARACTER; }
+ if(state<0) { return UNKNOWN_CHARACTER; }
+ return getCharImpl(state);
+ }
+ public final char getSuggestedChar(final char c) {
+ if(isGapChar(c)) {
+ return DEFAULT_GAP_CHARACTER;
+ }
+ return getPreferredCharImpl(c);
+ }
+ protected char getPreferredCharImpl(final char c) {
+ return getChar(getState(c));
+ }
+
+ //==========================================================
+ //================ ResidueDataType stuff ===================
+ //==========================================================
+
+ /**
+ * @return a copy of the input
+ */
+ public int[] getNucleotideStates(int[] array) {
+ if(array == null) { return null; }
+ int[] copy = new int[array.length];
+ System.arraycopy(array,0,copy,0,array.length);
+ return copy;
+ }
+
+ /**
+ * @return the input
+ */
+ public int getRelavantLength(int numberOfStates) {
+ return numberOfStates;
+ }
+ public int getSuggestedUnknownState() { return SUGGESTED_UNKNOWN_STATE; }
+
+ public final boolean hasGapCharacter() { return true; }
+ /**
+ * @return true if this character is a '.' or a '_'
+ */
+ public final boolean isGapChar(final char c) {
+ return isSuggestedGap(c);
+ }
+
+ public final boolean isUnknownChar(final char c) {
+ return isUnknownState(getState(c));
+ }
+ public final boolean isUnknownState(final int state) {
+ return(state==SUGGESTED_GAP_STATE||isUnknownStateCorr(state));
+ }
+ protected final boolean isUnknownStateCorr(final int state) {
+ return(state>=2);
+ }
+ /**
+ * @return a copy of theinput
+ */
+ public int[] getMolecularStatesFromSimpleNucleotides(int[] array, int startingIndex) {
+ if(array == null) { return null; }
+ int[] copy = new int[array.length-startingIndex];
+ System.arraycopy(array,startingIndex,copy,0,array.length-startingIndex);
+ return copy;
+ }
+ /**
+ * @return a copy of the input
+ */
+ public int[] getMolecularStatesFromIUPACNucleotides(int[] array, int startingIndex) {
+ if(array == null) { return null; }
+ int[] copy = new int[array.length-startingIndex];
+ System.arraycopy(array,startingIndex,copy,0,array.length-startingIndex);
+ return copy;
+ }
+
+ /**
+ * @return false Nucleotide data will suffice
+ */
+ public boolean isCreatesIUPACNuecleotides() {
+ return false;
+ }
+
+
+ /**
+ * @return 1
+ */
+ public final int getNucleotideLength() {
+ return 1;
+ }
+ // ====================================================================
+ // === Static utility methods
+ /**
+ * Obtain the complement state
+ * @param baseState the base state to complement (may be IUPAC but IUPACness is lost)
+ * @return the complement state
+ */
+ public static final int getComplementState(int baseState) {
+ switch(baseState) {
+ case A_STATE : return UT_STATE;
+ case UT_STATE : return A_STATE;
+ case G_STATE : return C_STATE;
+ case C_STATE : return G_STATE;
+ default: return UNKNOWN;
+ }
+ }
+ /**
+ * Obtain the complement of a sequence of nucleotides (or IUPACNucleotides - but IUPAC ness is lost)
+ * @param sequence the sequence (of nucleotide states)
+ * @return the complement
+ */
+ public static final int[] getSequenceComplement(int[] sequence) {
+ final int[] result = new int[sequence.length];
+ for(int i = 0 ; i < sequence.length ; i++) {
+ result[i] = getComplementState(sequence[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Complement of a sequence of nucleotides (or IUPACNucleotides - but IUPAC ness is lost)
+ * @param sequence the sequence (of nucleotide states) (is modified)
+ */
+ public static final void complementSequence(int[] sequence) {
+ for(int i = 0 ; i < sequence.length ; i++) {
+ sequence[i] = getComplementState(sequence[i]);
+ }
+ }
+ public static final boolean isSuggestedGap(char c) {
+ for(int i = 0 ; i < SUGGESTED_GAP_CHARACTERS.length ; i++) {
+ if(c==SUGGESTED_GAP_CHARACTERS[i]) { return true; }
+ }
+ return false;
+ }
+
+}
diff --git a/src/jade/data/NumericDataType.java b/src/jade/data/NumericDataType.java
new file mode 100644
index 0000000..0493f73
--- /dev/null
+++ b/src/jade/data/NumericDataType.java
@@ -0,0 +1,19 @@
+/*
+ * NumericDataType.java
+ *
+ * Created on April 18, 2005, 8:26 PM
+ */
+
+package jade.data;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class NumericDataType {
+
+ /** Creates a new instance of NumericDataType */
+ public NumericDataType() {
+ }
+
+}
diff --git a/src/jade/data/Sequence.java b/src/jade/data/Sequence.java
new file mode 100644
index 0000000..422b431
--- /dev/null
+++ b/src/jade/data/Sequence.java
@@ -0,0 +1,15 @@
+package jade.data;
+
+public class Sequence {
+ public Sequence (String id, String sequence){
+ this.id=id;
+ this.sequence=sequence;
+ }
+ public Sequence(){}
+ public String getID(){return id;}
+ public String getSeq(){return sequence;}
+ public void setID(String id){this.id = id;}
+ public void seqSeq(String seq){this.sequence = seq;}
+ private String id;
+ private String sequence;
+}
diff --git a/src/jade/data/SitePattern.java b/src/jade/data/SitePattern.java
new file mode 100644
index 0000000..89e36c4
--- /dev/null
+++ b/src/jade/data/SitePattern.java
@@ -0,0 +1,19 @@
+/*
+ * SitePattern.java
+ *
+ * Created on April 18, 2005, 9:00 PM
+ */
+
+package jade.data;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class SitePattern {
+
+ /** Creates a new instance of SitePattern */
+ public SitePattern() {
+ }
+
+}
diff --git a/src/jade/data/TransitionPenaltyTable.java b/src/jade/data/TransitionPenaltyTable.java
new file mode 100644
index 0000000..d7decb3
--- /dev/null
+++ b/src/jade/data/TransitionPenaltyTable.java
@@ -0,0 +1,24 @@
+// TransitionPenaltyTable.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jade.data;
+
+
+/**
+ * Implements a table of transition penalties for a particular datatype.
+ * Used for alignment scoring.
+ *
+ * @version $Id: TransitionPenaltyTable.java,v 1.2 2001/07/13 14:39:13 korbinian Exp $
+ *
+ * @author Alexei Drummond
+ */
+public interface TransitionPenaltyTable {
+
+ double penalty(int a, int b);
+ DataType getDataType();
+}
diff --git a/src/jade/lib/compile.sh b/src/jade/lib/compile.sh
new file mode 100644
index 0000000..e8eb8b1
--- /dev/null
+++ b/src/jade/lib/compile.sh
@@ -0,0 +1,3 @@
+gcc -c -I/home/smitty/apps/jdk/jdk1.6.0/include -I/home/smitty/apps/jdk/jdk1.6.0/include/linux -o libmatrixExp.o jade_reconstruct_area_PJNI.c
+ld -shared -o libmatrixExp.so libmatrixExp.o -lm -lgsl -lgslcblas
+
diff --git a/src/jade/lib/instr b/src/jade/lib/instr
new file mode 100644
index 0000000..b35b2ae
--- /dev/null
+++ b/src/jade/lib/instr
@@ -0,0 +1 @@
+use java -Djava.library.path=lib/ -jar jarfile
diff --git a/src/jade/lib/jade_reconstruct_area_PJNI.c b/src/jade/lib/jade_reconstruct_area_PJNI.c
new file mode 100644
index 0000000..d5bf635
--- /dev/null
+++ b/src/jade/lib/jade_reconstruct_area_PJNI.c
@@ -0,0 +1,35 @@
+#include <jni.h>
+#include "jade_reconstruct_area_PJNI.h"
+#include <gsl/gsl_linalg.h>
+#include <stdio.h>
+
+JNIEXPORT jdoubleArray JNICALL Java_jade_reconstruct_area_PJNI_matrixExp
+ (JNIEnv *jenv, jobject obj, jdoubleArray arr,jint size){
+ jdoubleArray ret = (*jenv)->NewDoubleArray(jenv,(jint)( size * size));
+ double *OutData = (*jenv)->GetDoubleArrayElements(jenv,ret,JNI_FALSE);
+ //double OutData [size*size];
+ jdouble * at_data = (*jenv)->GetDoubleArrayElements(jenv, arr, NULL);
+ int i,j;
+ double a_data [size*size];
+ for (i = 0; i < size*size; i++){
+ a_data[i] = (double)at_data[i];
+ }
+ gsl_matrix_view m = gsl_matrix_view_array (a_data, (int)size, (int)size);
+ gsl_mode_t mt = 0;
+ gsl_matrix *ma = gsl_matrix_alloc (size,size);
+ gsl_linalg_exponential_ss(&m.matrix, ma, mt);//m input, ma output
+
+ int x = 0;
+ for(i = 0; i < size; i++){
+ for(j=0;j<size;j++){
+ OutData[x] = gsl_matrix_get(ma,i,j);
+ x++;
+ }
+ }
+ //gsl_permutation_free (ma);
+ //(*jenv)->SetDoubleArrayRegion(jenv,ret,(jsize)0,(jsize)size*size,OutData);
+ (*jenv)->ReleaseDoubleArrayElements(jenv,ret,OutData,0);
+ (*jenv)->ReleaseDoubleArrayElements(jenv,arr,at_data,0);
+ gsl_matrix_free (ma);
+ return ret;
+}
diff --git a/src/jade/lib/jade_reconstruct_area_PJNI.h b/src/jade/lib/jade_reconstruct_area_PJNI.h
new file mode 100644
index 0000000..1395933
--- /dev/null
+++ b/src/jade/lib/jade_reconstruct_area_PJNI.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class jade_reconstruct_area_PJNI */
+
+#ifndef _Included_jade_reconstruct_area_PJNI
+#define _Included_jade_reconstruct_area_PJNI
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: jade_reconstruct_area_PJNI
+ * Method: matrixExp
+ * Signature: ([DI)[D
+ */
+JNIEXPORT jdoubleArray JNICALL Java_jade_reconstruct_area_PJNI_matrixExp
+ (JNIEnv *, jobject, jdoubleArray, jint);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/jade/lib/libmatrixExp.so b/src/jade/lib/libmatrixExp.so
new file mode 100755
index 0000000..2fe15a0
Binary files /dev/null and b/src/jade/lib/libmatrixExp.so differ
diff --git a/src/jade/math/AP_Basic.java b/src/jade/math/AP_Basic.java
new file mode 100644
index 0000000..ec2dc92
--- /dev/null
+++ b/src/jade/math/AP_Basic.java
@@ -0,0 +1,421 @@
+package jade.math;
+
+public class AP_Basic {
+ public static void inivec(int l, int u, double a[], double x) {
+ for (; l <= u; l++)
+ a[l] = x;
+ }
+
+ public static void inimat(int lr, int ur, int lc, int uc, double a[][],
+ double x) {
+ int j;
+ for (; lr <= ur; lr++)
+ for (j = lc; j <= uc; j++)
+ a[lr][j] = x;
+ }
+
+ public static void dupvec(int l, int u, int shift, double a[], double b[]) {
+ for (; l <= u; l++)
+ a[l] = b[l + shift];
+ }
+
+ public static void dupcolvec(int l, int u, int j, double a[][], double b[]) {
+ for (; l <= u; l++)
+ a[l][j] = b[l];
+ }
+
+ public static void dupmat(int l, int u, int i, int j, double a[][],
+ double b[][]) {
+ int k;
+ for (; l <= u; l++)
+ for (k = i; k <= j; k++)
+ a[l][k] = b[l][k];
+ }
+
+ public static void mulrow(int l, int u, int i, int j, double a[][],
+ double b[][], double x) {
+ for (; l <= u; l++)
+ a[i][l] = b[j][l] * x;
+ }
+
+ public static void mulcol(int l, int u, int i, int j, double a[][],
+ double b[][], double x) {
+ for (; l <= u; l++)
+ a[l][i] = b[l][j] * x;
+ }
+
+ public static double vecvec(int l, int u, int shift, double a[], double b[]) {
+ int k;
+ double s;
+ s = 0.0;
+ for (k = l; k <= u; k++)
+ s += a[k] * b[k + shift];
+ return (s);
+ }
+
+ public static double tammat(int l, int u, int i, int j, double a[][],
+ double b[][]) {
+ int k;
+ double s;
+ s = 0.0;
+ for (k = l; k <= u; k++)
+ s += a[k][i] * b[k][j];
+ return (s);
+ }
+
+ public static double mattam(int l, int u, int i, int j, double a[][],
+ double b[][]) {
+ int k;
+ double s;
+ s = 0.0;
+ for (k = l; k <= u; k++)
+ s += a[i][k] * b[j][k];
+ return (s);
+ }
+
+ public static void ichrowcol(int l, int u, int i, int j, double a[][]) {
+ double r;
+ for (; l <= u; l++) {
+ r = a[i][l];
+ a[i][l] = a[l][j];
+ a[l][j] = r;
+ }
+ }
+
+ public static void elmveccol(int l, int u, int i, double a[], double b[][],
+ double x) {
+ for (; l <= u; l++)
+ a[l] += b[l][i] * x;
+ }
+
+ public static int qrisngval(double a[][], int m, int n, double val[],
+ double em[]) {
+ int i;
+ double b[] = new double[n + 1];
+ hshreabid(a, m, n, val, b, em);
+ i = qrisngvalbid(val, b, n, em);
+ return i;
+ }
+
+ public static int qrisngvalbid(double d[], double b[], int n, double em[]) {
+ int n1, k, k1, i, i1, count, max, rnk;
+ double tol, bmax, z, x, y, g, h, f, c, s, min;
+ tol = em[2] * em[1];
+ count = 0;
+ bmax = 0.0;
+ max = (int) em[4];
+ min = em[6];
+ rnk = n;
+ do {
+ k = n;
+ n1 = n - 1;
+ while (true) {
+ k--;
+ if (k <= 0)
+ break;
+ if (Math.abs(b[k]) >= tol) {
+ if (Math.abs(d[k]) < tol) {
+ c = 0.0;
+ s = 1.0;
+ for (i = k; i <= n1; i++) {
+ f = s * b[i];
+ b[i] *= c;
+ i1 = i + 1;
+ if (Math.abs(f) < tol)
+ break;
+ g = d[i1];
+ d[i1] = h = Math.sqrt(f * f + g * g);
+ c = g / h;
+ s = -f / h;
+ }
+ break;
+ }
+ } else {
+ if (Math.abs(b[k]) > bmax)
+ bmax = Math.abs(b[k]);
+ break;
+ }
+ }
+ if (k == n1) {
+ if (d[n] < 0.0)
+ d[n] = -d[n];
+ if (d[n] <= min)
+ rnk--;
+ n = n1;
+ } else {
+ count++;
+ if (count > max)
+ break;
+ k1 = k + 1;
+ z = d[n];
+ x = d[k1];
+ y = d[n1];
+ g = (n1 == 1) ? 0.0 : b[n1 - 1];
+ h = b[n1];
+ f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y);
+ g = Math.sqrt(f * f + 1.0);
+ f = ((x - z) * (x + z) + h
+ * (y / ((f < 0.0) ? f - g : f + g) - h))
+ / x;
+ c = s = 1.0;
+ for (i = k1 + 1; i <= n; i++) {
+ i1 = i - 1;
+ g = b[i1];
+ y = d[i];
+ h = s * g;
+ g *= c;
+ z = Math.sqrt(f * f + h * h);
+ c = f / z;
+ s = h / z;
+ if (i1 != k1)
+ b[i1 - 1] = z;
+ f = x * c + g * s;
+ g = g * c - x * s;
+ h = y * s;
+ y *= c;
+ d[i1] = z = Math.sqrt(f * f + h * h);
+ c = f / z;
+ s = h / z;
+ f = c * g + s * y;
+ x = c * y - s * g;
+ }
+ b[n1] = f;
+ d[n] = x;
+ }
+ } while (n > 0);
+ em[3] = bmax;
+ em[5] = count;
+ em[7] = rnk;
+ return n;
+ }
+
+ public static void hshreabid(double a[][], int m, int n, double d[],
+ double b[], double em[]) {
+ int i, j, i1;
+ double norm, machtol, w, s, f, g, h;
+ norm = 0.0;
+ for (i = 1; i <= m; i++) {
+ w = 0.0;
+ for (j = 1; j <= n; j++)
+ w += Math.abs(a[i][j]);
+ if (w > norm)
+ norm = w;
+ }
+ machtol = em[0] * norm;
+ em[1] = norm;
+ for (i = 1; i <= n; i++) {
+ i1 = i + 1;
+ s = tammat(i1, m, i, i, a, a);
+ if (s < machtol)
+ d[i] = a[i][i];
+ else {
+ f = a[i][i];
+ s += f * f;
+ d[i] = g = (f < 0.0) ? Math.sqrt(s) : -Math.sqrt(s);
+ h = f * g - s;
+ a[i][i] = f - g;
+ for (j = i1; j <= n; j++)
+ elmcol(i, m, j, i, a, a, tammat(i, m, i, j, a, a) / h);
+ }
+ if (i < n) {
+ s = mattam(i1 + 1, n, i, i, a, a);
+ if (s < machtol)
+ b[i] = a[i][i1];
+ else {
+ f = a[i][i1];
+ s += f * f;
+ b[i] = g = (f < 0.0) ? Math.sqrt(s) : -Math.sqrt(s);
+ h = f * g - s;
+ a[i][i1] = f - g;
+ for (j = i1; j <= m; j++)
+ elmrow(i1, n, j, i, a, a, mattam(i1, n, i, j, a, a) / h);
+ }
+ }
+ }
+ }
+
+ public static void elmcol(int l, int u, int i, int j, double a[][],
+ double b[][], double x) {
+ for (; l <= u; l++)
+ a[l][i] += b[l][j] * x;
+ }
+
+ public static void elmrow(int l, int u, int i, int j, double a[][],
+ double b[][], double x) {
+ for (; l <= u; l++)
+ a[i][l] += b[j][l] * x;
+ }
+
+ public static int qrisngvaldec(double a[][], int m, int n, double val[],
+ double v[][], double em[]) {
+ int i;
+ double b[] = new double[n + 1];
+ hshreabid(a, m, n, val, b, em);
+ psttfmmat(a, n, v, b);
+ pretfmmat(a, m, n, val);
+ i = qrisngvaldecbid(val, b, m, n, a, v, em);
+ return i;
+ }
+
+ public static int qrisngvaldecbid(double d[], double b[], int m, int n,
+ double u[][], double v[][], double em[]) {
+ int n0, n1, k, k1, i, i1, count, max, rnk;
+ double tol, bmax, z, x, y, g, h, f, c, s, min;
+ tol = em[2] * em[1];
+ count = 0;
+ bmax = 0.0;
+ max = (int) em[4];
+ min = em[6];
+ rnk = n0 = n;
+ do {
+ k = n;
+ n1 = n - 1;
+ while (true) {
+ k--;
+ if (k <= 0)
+ break;
+ if (Math.abs(b[k]) >= tol) {
+ if (Math.abs(d[k]) < tol) {
+ c = 0.0;
+ s = 1.0;
+ for (i = k; i <= n1; i++) {
+ f = s * b[i];
+ b[i] *= c;
+ i1 = i + 1;
+ if (Math.abs(f) < tol)
+ break;
+ g = d[i1];
+ d[i1] = h = Math.sqrt(f * f + g * g);
+ c = g / h;
+ s = -f / h;
+ rotcol(1, m, k, i1, u, c, s);
+ }
+ break;
+ }
+ } else {
+ if (Math.abs(b[k]) > bmax)
+ bmax = Math.abs(b[k]);
+ break;
+ }
+ }
+ if (k == n1) {
+ if (d[n] < 0.0) {
+ d[n] = -d[n];
+ for (i = 1; i <= n0; i++)
+ v[i][n] = -v[i][n];
+ }
+ if (d[n] <= min)
+ rnk--;
+ n = n1;
+ } else {
+ count++;
+ if (count > max)
+ break;
+ k1 = k + 1;
+ z = d[n];
+ x = d[k1];
+ y = d[n1];
+ g = (n1 == 1) ? 0.0 : b[n1 - 1];
+ h = b[n1];
+ f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y);
+ g = Math.sqrt(f * f + 1.0);
+ f = ((x - z) * (x + z) + h
+ * (y / ((f < 0.0) ? f - g : f + g) - h))
+ / x;
+ c = s = 1.0;
+ for (i = k1 + 1; i <= n; i++) {
+ i1 = i - 1;
+ g = b[i1];
+ y = d[i];
+ h = s * g;
+ g *= c;
+ z = Math.sqrt(f * f + h * h);
+ c = f / z;
+ s = h / z;
+ if (i1 != k1)
+ b[i1 - 1] = z;
+ f = x * c + g * s;
+ g = g * c - x * s;
+ h = y * s;
+ y *= c;
+ rotcol(1, n0, i1, i, v, c, s);
+ d[i1] = z = Math.sqrt(f * f + h * h);
+ c = f / z;
+ s = h / z;
+ f = c * g + s * y;
+ x = c * y - s * g;
+ rotcol(1, m, i1, i, u, c, s);
+ }
+ b[n1] = f;
+ d[n] = x;
+ }
+ } while (n > 0);
+ em[3] = bmax;
+ em[5] = count;
+ em[7] = rnk;
+ return n;
+ }
+
+ public static void pretfmmat(double a[][], int m, int n, double d[]) {
+ int i, i1, j;
+ double g, h;
+ for (i = n; i >= 1; i--) {
+ i1 = i + 1;
+ g = d[i];
+ h = g * a[i][i];
+ for (j = i1; j <= n; j++)
+ a[i][j] = 0.0;
+ if (h < 0.0) {
+ for (j = i1; j <= n; j++)
+ elmcol(i, m, j, i, a, a, tammat(i1, m, i, j, a, a) / h);
+ for (j = i; j <= m; j++)
+ a[j][i] /= g;
+ } else
+ for (j = i; j <= m; j++)
+ a[j][i] = 0.0;
+ a[i][i] += 1.0;
+ }
+ }
+
+ public static void psttfmmat(double a[][], int n, double v[][], double b[]) {
+ int i, i1, j;
+ double h;
+ i1 = n;
+ v[n][n] = 1.0;
+ for (i = n - 1; i >= 1; i--) {
+ h = b[i] * a[i][i1];
+ if (h < 0.0) {
+ for (j = i1; j <= n; j++)
+ v[j][i] = a[i][j] / h;
+ for (j = i1; j <= n; j++)
+ elmcol(i1, n, j, i, v, v, matmat(i1, n, i, j, a, v));
+ }
+ for (j = i1; j <= n; j++)
+ v[i][j] = v[j][i] = 0.0;
+ v[i][i] = 1.0;
+ i1 = i;
+ }
+ }
+
+ public static double matmat(int l, int u, int i, int j, double a[][],
+ double b[][]) {
+ int k;
+ double s;
+ s = 0.0;
+ for (k = l; k <= u; k++)
+ s += a[i][k] * b[k][j];
+ return (s);
+ }
+
+ public static void rotcol(int l, int u, int i, int j, double a[][],
+ double c, double s) {
+ double x, y;
+ for (; l <= u; l++) {
+ x = a[l][i];
+ y = a[l][j];
+ a[l][i] = x * c + y * s;
+ a[l][j] = y * c - x * s;
+ }
+ }
+
+}
diff --git a/src/jade/math/AP_Praxis.java b/src/jade/math/AP_Praxis.java
new file mode 100644
index 0000000..6ce0d89
--- /dev/null
+++ b/src/jade/math/AP_Praxis.java
@@ -0,0 +1,420 @@
+package jade.math;
+
+import java.util.*;
+
+public class AP_Praxis {
+ public static void praxis(int n, double x[], AP_praxis_method method,
+ double in[], double out[])
+
+ {
+ boolean illc, emergency;
+ int i, j, k, k2, maxf, kl, kt, ktm;
+ double s, sl, dn, dmin, f1, lds, ldt, sf, df, qf1, qd0, qd1, m2, m4, small, vsmall, large, vlarge, scbd, ldfac, t2, macheps, reltol, abstol, h, l;
+ int nl[] = new int[1];
+ int nf[] = new int[1];
+ double em[] = new double[8];
+ double d[] = new double[n + 1];
+ double y[] = new double[n + 1];
+ double z[] = new double[n + 1];
+ double q0[] = new double[n + 1];
+ double q1[] = new double[n + 1];
+ double v[][] = new double[n + 1][n + 1];
+ double a[][] = new double[n + 1][n + 1];
+ double qa[] = new double[1];
+ double qb[] = new double[1];
+ double qc[] = new double[1];
+ double fx[] = new double[1];
+ double tmp1[] = new double[1];
+ double tmp2[] = new double[1];
+ double tmp3[] = new double[1];
+ Random ran = new Random(1);
+ macheps = in[0];
+ reltol = in[1];
+ abstol = in[2];
+ maxf = (int) in[5];
+ h = in[6];
+ scbd = in[7];
+ ktm = (int) in[8];
+ illc = in[9] < 0.0;
+ small = macheps * macheps;
+ vsmall = small * small;
+ large = 1.0 / small;
+ vlarge = 1.0 / vsmall;
+ m2 = reltol;
+ m4 = Math.sqrt(m2);
+ ldfac = (illc ? 0.1 : 0.01);
+ kt = nl[0] = 0;
+ nf[0] = 1;
+ out[3] = qf1 = fx[0] = method.funct(n, x);
+ abstol = t2 = small + Math.abs(abstol);
+ dmin = small;
+ if (h < abstol * 100.0)
+ h = abstol * 100;
+ ldt = h;
+ AP_Basic.inimat(1, n, 1, n, v, 0.0);
+ for (i = 1; i <= n; i++)
+ v[i][i] = 1.0;
+ d[1] = qd0 = qd1 = 0.0;
+ AP_Basic.dupvec(1, n, 0, q1, x);
+ AP_Basic.inivec(1, n, q0, 0.0);
+ emergency = false;
+ while (true) {
+ sf = d[1];
+ d[1] = s = 0.0;
+ tmp1[0] = d[1];
+ tmp2[0] = s;
+ praxismin(1, 2, tmp1, tmp2, fx, false, n, x, v, qa, qb, qc, qd0,
+ qd1, q0, q1, nf, nl, fx, m2, m4, dmin, ldt, reltol, abstol,
+ small, h, method);
+ d[1] = tmp1[0];
+ s = tmp2[0];
+ if (s <= 0.0)
+ AP_Basic.mulcol(1, n, 1, 1, v, v, -1.0);
+ if (sf <= 0.9 * d[1] || 0.9 * sf >= d[1])
+ AP_Basic.inivec(2, n, d, 0.0);
+ for (k = 2; k <= n; k++) {
+ AP_Basic.dupvec(1, n, 0, y, x);
+ sf = fx[0];
+ illc = (illc || kt > 0);
+ while (true) {
+ kl = k;
+ df = 0.0;
+ if (illc) {
+ /* random stop to get off resulting valley */
+ for (i = 1; i <= n; i++) {
+ s = z[i] = (0.1 * ldt + t2 * Math.pow(10.0, kt))
+ * (ran.nextDouble() - 0.5);
+ AP_Basic.elmveccol(1, n, i, x, v, s);
+ }
+ fx[0] = method.funct(n, x);
+ nf[0]++;
+ }
+ for (k2 = k; k2 <= n; k2++) {
+ sl = fx[0];
+ s = 0.0;
+ tmp1[0] = d[k2];
+ tmp2[0] = s;
+ praxismin(k2, 2, tmp1, tmp2, fx, false, n, x, v, qa,
+ qb, qc, qd0, qd1, q0, q1, nf, nl, fx, m2, m4,
+ dmin, ldt, reltol, abstol, small, h, method);
+ d[k2] = tmp1[0];
+ s = tmp2[0];
+ s = illc ? d[k2] * (s + z[k2]) * (s + z[k2]) : sl
+ - fx[0];
+ if (df < s) {
+ df = s;
+ kl = k2;
+ }
+ }
+ if (!illc && df < Math.abs(100.0 * macheps * fx[0]))
+ illc = true;
+ else
+ break;
+ }
+ for (k2 = 1; k2 <= k - 1; k2++) {
+ s = 0.0;
+ tmp1[0] = d[k2];
+ tmp2[0] = s;
+ praxismin(k2, 2, tmp1, tmp2, fx, false, n, x, v, qa, qb,
+ qc, qd0, qd1, q0, q1, nf, nl, fx, m2, m4, dmin,
+ ldt, reltol, abstol, small, h, method);
+ d[k2] = tmp1[0];
+ s = tmp2[0];
+ }
+ f1 = fx[0];
+ fx[0] = sf;
+ lds = 0.0;
+ for (i = 1; i <= n; i++) {
+ sl = x[i];
+ x[i] = y[i];
+ y[i] = sl -= y[i];
+ lds += sl * sl;
+ }
+ lds = Math.sqrt(lds);
+ if (lds > small) {
+ for (i = kl - 1; i >= k; i--) {
+ for (j = 1; j <= n; j++)
+ v[j][i + 1] = v[j][i];
+ d[i + 1] = d[i];
+ }
+ d[k] = 0.0;
+ AP_Basic.dupcolvec(1, n, k, v, y);
+ AP_Basic.mulcol(1, n, k, k, v, v, 1.0 / lds);
+ tmp1[0] = d[k];
+ tmp2[0] = lds;
+ tmp3[0] = f1;
+ praxismin(k, 4, tmp1, tmp2, tmp3, true, n, x, v, qa, qb,
+ qc, qd0, qd1, q0, q1, nf, nl, fx, m2, m4, dmin,
+ ldt, reltol, abstol, small, h, method);
+ d[k] = tmp1[0];
+ lds = tmp2[0];
+ f1 = tmp3[0];
+ if (lds <= 0.0) {
+ lds = -lds;
+ AP_Basic.mulcol(1, n, k, k, v, v, -1.0);
+ }
+ }
+ ldt *= ldfac;
+ if (ldt < lds)
+ ldt = lds;
+ t2 = m2 * Math.sqrt(AP_Basic.vecvec(1, n, 0, x, x)) + abstol;
+ kt = (ldt > 0.5 * t2) ? 0 : kt + 1;
+ if (kt > ktm) {
+ out[1] = 0.0;
+ emergency = true;
+ }
+ }
+ if (emergency)
+ break;
+ /* quad */
+ s = fx[0];
+ fx[0] = qf1;
+ qf1 = s;
+ qd1 = 0.0;
+ for (i = 1; i <= n; i++) {
+ s = x[i];
+ x[i] = l = q1[i];
+ q1[i] = s;
+ qd1 += (s - l) * (s - l);
+ }
+ l = qd1 = Math.sqrt(qd1);
+ s = 0.0;
+ if ((qd0 * qd1 > Double.MIN_VALUE) && (nl[0] >= 3 * n * n)) {
+ tmp1[0] = s;
+ tmp2[0] = l;
+ tmp3[0] = qf1;
+ praxismin(0, 2, tmp1, tmp2, tmp3, true, n, x, v, qa, qb, qc,
+ qd0, qd1, q0, q1, nf, nl, fx, m2, m4, dmin, ldt,
+ reltol, abstol, small, h, method);
+ s = tmp1[0];
+ l = tmp2[0];
+ qf1 = tmp3[0];
+ qa[0] = l * (l - qd1) / (qd0 * (qd0 + qd1));
+ qb[0] = (l + qd0) * (qd1 - l) / (qd0 * qd1);
+ qc[0] = l * (l + qd0) / (qd1 * (qd0 + qd1));
+ } else {
+ fx[0] = qf1;
+ qa[0] = qb[0] = 0.0;
+ qc[0] = 1.0;
+ }
+ qd0 = qd1;
+ for (i = 1; i <= n; i++) {
+ s = q0[i];
+ q0[i] = x[i];
+ x[i] = qa[0] * s + qb[0] * x[i] + qc[0] * q1[i];
+ }
+ /* end of quad */
+ dn = 0.0;
+ for (i = 1; i <= n; i++) {
+ d[i] = 1.0 / Math.sqrt(d[i]);
+ if (dn < d[i])
+ dn = d[i];
+ }
+ for (j = 1; j <= n; j++) {
+ s = d[j] / dn;
+ AP_Basic.mulcol(1, n, j, j, v, v, s);
+ }
+ if (scbd > 1.0) {
+ s = vlarge;
+ for (i = 1; i <= n; i++) {
+ sl = z[i] = Math.sqrt(AP_Basic.mattam(1, n, i, i, v, v));
+ if (sl < m4)
+ z[i] = m4;
+ if (s > sl)
+ s = sl;
+ }
+ for (i = 1; i <= n; i++) {
+ sl = s / z[i];
+ z[i] = 1.0 / sl;
+ if (z[i] > scbd) {
+ sl = 1.0 / scbd;
+ z[i] = scbd;
+ }
+ AP_Basic.mulrow(1, n, i, i, v, v, sl);
+ }
+ }
+ for (i = 1; i <= n; i++)
+ AP_Basic.ichrowcol(i + 1, n, i, i, v);
+ em[0] = em[2] = macheps;
+ em[4] = 10 * n;
+ em[6] = vsmall;
+ AP_Basic.dupmat(1, n, 1, n, a, v);
+ if (AP_Basic.qrisngvaldec(a, n, n, d, v, em) != 0) {
+ out[1] = 2.0;
+ emergency = true;
+ }
+ if (emergency)
+ break;
+ if (scbd > 1.0) {
+ for (i = 1; i <= n; i++)
+ AP_Basic.mulrow(1, n, i, i, v, v, z[i]);
+ for (i = 1; i <= n; i++) {
+ s = Math.sqrt(AP_Basic.tammat(1, n, i, i, v, v));
+ d[i] *= s;
+ s = 1.0 / s;
+ AP_Basic.mulcol(1, n, i, i, v, v, s);
+ }
+ }
+ for (i = 1; i <= n; i++) {
+ s = dn * d[i];
+ d[i] = (s > large) ? vsmall : ((s < small) ? vlarge
+ : 1.0 / (s * s));
+ }
+ /* sort */
+ for (i = 1; i <= n - 1; i++) {
+ k = i;
+ s = d[i];
+ for (j = i + 1; j <= n; j++)
+ if (d[j] > s) {
+ k = j;
+ s = d[j];
+ }
+ if (k > i) {
+ d[k] = d[i];
+ d[i] = s;
+ for (j = 1; j <= n; j++) {
+ s = v[j][i];
+ v[j][i] = v[j][k];
+ v[j][k] = s;
+ }
+ }
+ }
+ /* end of sort */
+ dmin = d[n];
+ if (dmin < small)
+ dmin = small;
+ illc = (m2 * d[1]) > dmin;
+ if (nf[0] >= maxf) {
+ out[1] = 1.0;
+ break;
+ }
+ }
+ out[2] = fx[0];
+ out[4] = nf[0];
+ out[5] = nl[0];
+ out[6] = ldt;
+ }
+
+ static private void praxismin(int j, int nits, double d2[], double x1[],
+ double f1[], boolean fk, int n, double x[], double v[][],
+ double qa[], double qb[], double qc[], double qd0, double qd1,
+ double q0[], double q1[], int nf[], int nl[], double fx[],
+ double m2, double m4, double dmin, double ldt, double reltol,
+ double abstol, double small, double h, AP_praxis_method method) {
+ /* this procedure is internally used by PRAXIS */
+ boolean loop, dz;
+ int k;
+ double x2, xm, f0, f2, fm, d1, t2, s, sf1, sx1;
+ f2 = x2 = 0.0;
+ sf1 = f1[0];
+ sx1 = x1[0];
+ k = 0;
+ xm = 0.0;
+ f0 = fm = fx[0];
+ dz = d2[0] < reltol;
+ s = Math.sqrt(AP_Basic.vecvec(1, n, 0, x, x));
+ t2 = m4 * Math.sqrt(Math.abs(fx[0]) / (dz ? dmin : d2[0]) + s * ldt)
+ + m2 * ldt;
+ s = s * m4 + abstol;
+ if (dz && (t2 > s))
+ t2 = s;
+ if (t2 < small)
+ t2 = small;
+ if (t2 > 0.01 * h)
+ t2 = 0.01 * h;
+ if (fk && (f1[0] <= fm)) {
+ xm = x1[0];
+ fm = f1[0];
+ }
+ if (!fk || (Math.abs(x1[0]) < t2)) {
+ x1[0] = (x1[0] > 0.0) ? t2 : -t2;
+ f1[0] = praxisflin(x1[0], j, n, x, v, qa, qb, qc, qd0, qd1, q0, q1,
+ nf, method);
+ }
+ if (f1[0] <= fm) {
+ xm = x1[0];
+ fm = f1[0];
+ }
+ loop = true;
+ while (loop) {
+ if (dz) {
+ /*
+ * evaluate praxisflin at another point and estimate the second
+ * derivative
+ */
+ x2 = (f0 < f1[0]) ? -x1[0] : x1[0] * 2.0;
+ f2 = praxisflin(x2, j, n, x, v, qa, qb, qc, qd0, qd1, q0, q1,
+ nf, method);
+ if (f2 <= fm) {
+ xm = x2;
+ fm = f2;
+ }
+ d2[0] = (x2 * (f1[0] - f0) - x1[0] * (f2 - f0))
+ / (x1[0] * x2 * (x1[0] - x2));
+ }
+ /* estimate first derivative at 0 */
+ d1 = (f1[0] - f0) / x1[0] - x1[0] * d2[0];
+ dz = true;
+ x2 = (d2[0] <= small) ? ((d1 < 0.0) ? h : -h) : -0.5 * d1 / d2[0];
+ if (Math.abs(x2) > h)
+ x2 = (x2 > 0.0) ? h : -h;
+ while (true) {
+ f2 = praxisflin(x2, j, n, x, v, qa, qb, qc, qd0, qd1, q0, q1,
+ nf, method);
+ if (k < nits && f2 > f0) {
+ k++;
+ if (f0 < f1[0] && x1[0] * x2 > 0.0)
+ break;
+ x2 = 0.5 * x2;
+ } else {
+ loop = false;
+ break;
+ }
+ }
+ }
+ nl[0]++;
+ if (f2 > fm)
+ x2 = xm;
+ else
+ fm = f2;
+ d2[0] = (Math.abs(x2 * (x2 - x1[0])) > small) ? ((x2 * (f1[0] - f0) - x1[0]
+ * (fm - f0)) / (x1[0] * x2 * (x1[0] - x2)))
+ : ((k > 0) ? 0.0 : d2[0]);
+ if (d2[0] <= small)
+ d2[0] = small;
+ x1[0] = x2;
+ fx[0] = fm;
+ if (sf1 < fx[0]) {
+ fx[0] = sf1;
+ x1[0] = sx1;
+ }
+ if (j > 0)
+ AP_Basic.elmveccol(1, n, j, x, v, x1[0]);
+ }
+
+ static private double praxisflin(double l, int j, int n, double x[],
+ double v[][], double qa[], double qb[], double[] qc, double qd0,
+ double qd1, double q0[], double q1[], int nf[],
+ AP_praxis_method method) {
+ /* this procedure is internally used by PRAXISMIN */
+ int i;
+ double result;
+ double t[] = new double[n + 1];
+ if (j > 0)
+ for (i = 1; i <= n; i++)
+ t[i] = x[i] + l * v[i][j];
+ else {
+ /* search along parabolic space curve */
+ qa[0] = l * (l - qd1) / (qd0 * (qd0 + qd1));
+ qb[0] = (l + qd0) * (qd1 - l) / (qd0 * qd1);
+ qc[0] = l * (l + qd0) / (qd1 * (qd0 + qd1));
+ for (i = 1; i <= n; i++)
+ t[i] = qa[0] * q0[i] + qb[0] * x[i] + qc[0] * q1[i];
+ }
+ nf[0]++;
+ result = method.funct(n, t);
+ return result;
+ }
+
+
+}
diff --git a/src/jade/math/AP_praxis_method.java b/src/jade/math/AP_praxis_method.java
new file mode 100644
index 0000000..f0cab23
--- /dev/null
+++ b/src/jade/math/AP_praxis_method.java
@@ -0,0 +1,6 @@
+package jade.math;
+
+public interface AP_praxis_method {
+
+ public double funct(int n, double x[]);
+}
\ No newline at end of file
diff --git a/src/jade/math/BetaDistribution.java b/src/jade/math/BetaDistribution.java
new file mode 100644
index 0000000..80772bf
--- /dev/null
+++ b/src/jade/math/BetaDistribution.java
@@ -0,0 +1,51 @@
+/*
+ * BetaDistribution.java
+ *
+ * Created on August 9, 2005, 3:58 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class BetaDistribution implements ProbDistribution{
+
+ /** Creates a new instance of BetaDistribution */
+ public BetaDistribution( double shape1, double shape2) {
+ alpha1 = shape1;
+ alpha2 = shape2;
+ norm = GammaFunction.logBeta(alpha1, alpha2);
+ gamma1 = new GammaDistribution( alpha1, 1.0 );
+ gamma2 = new GammaDistribution( alpha2, 1.0 );
+ }
+
+ public void setShape1(double shape1){
+ alpha1 = shape1;
+ }
+ public void setShape2(double shape2){
+ alpha2 = shape2;
+ }
+ public double getShape1(){ return alpha1; }
+ public double getShape2(){ return alpha2; }
+ public double getValue(){
+ double y1 = gamma1.getValue();
+ double y2 = gamma2.getValue();
+ return y1 / (y1 + y2);
+ }
+
+ public double getPDF(double x){
+ return x;
+ }
+
+ //private methods
+ private double alpha1;
+ private double alpha2;
+ private double norm;
+ private GammaDistribution gamma1;
+ private GammaDistribution gamma2;
+
+}
diff --git a/src/jade/math/ContinuedFraction.java b/src/jade/math/ContinuedFraction.java
new file mode 100644
index 0000000..c5ba9ff
--- /dev/null
+++ b/src/jade/math/ContinuedFraction.java
@@ -0,0 +1,58 @@
+/*
+ * ContinuedFraction.java
+ *
+ * Created on August 19, 2005, 12:31 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public abstract class ContinuedFraction extends IterativeProcess{
+
+ /** Creates a new instance of ContinuedFraction */
+ public ContinuedFraction() {
+ }
+
+ protected abstract void computeFactorsAt(int n);
+
+ public double evaluateIteration(){
+ computeFactorsAt(getIterations());
+ denominator = 1/limitedSmallValue(factors[0]*denominator+factors[1]);
+ numerator = limitedSmallValue(factors[0]/numerator+factors[1]);
+ double delta = numerator * denominator;
+ result *=delta;
+ return Math.abs(delta-1);
+ }
+
+ public double getResult(){ return result; }
+
+ public void initializeIterations(){
+ numerator = limitedSmallValue(initialValue());
+ denominator = 0;
+ result = numerator;
+ return;
+ }
+
+ protected abstract double initialValue();
+
+ private double limitedSmallValue(double r){
+ return Math.abs(r)<MachinePrecision.smallNumber()
+ ? MachinePrecision.smallNumber(): r;
+ }
+
+ public void setArgument(double r){
+ x = r;
+ return;
+ }
+
+ private double result;
+ protected double x;
+ private double numerator;
+ private double denominator;
+ protected double [] factors = new double [2];
+}
diff --git a/src/jade/math/ExponentialDistribution.java b/src/jade/math/ExponentialDistribution.java
new file mode 100644
index 0000000..628b10b
--- /dev/null
+++ b/src/jade/math/ExponentialDistribution.java
@@ -0,0 +1,48 @@
+/*
+ * ExponentialDistribution.java
+ *
+ * Created on August 9, 2005, 3:24 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+import java.util.*;
+/**
+ *
+ * @author stephensmith
+ */
+public class ExponentialDistribution implements ProbDistribution{
+
+ /** Creates a new instance of ExponentialDistribution */
+ public ExponentialDistribution() {
+ }
+ public ExponentialDistribution( double beta ){
+ if(beta<=0)
+ throw new IllegalArgumentException(" Exponential fall-off must be positive ( function was sent a negative value ) ");
+ else
+ this.beta = beta;
+ }
+ /**
+ *@param beta the falloff value for the exponential distribution
+ */
+ public void setFallOff( double beta ){
+ this.beta = beta;
+ }
+
+ public double getFallOff(){ return beta; }
+
+ public double getValue(){
+ return -beta * Math.log(r.nextDouble());
+ }
+
+ //
+ //probability of finding something smaller than x
+ //@return the intregral of the probability distribution function from 0 to x
+ public double getPDF(double x){
+ return (1/beta)*Math.exp(-(1/beta)*x);
+ }
+ private double beta;
+ private Random r = new Random();
+}
diff --git a/src/jade/math/GammaDistribution.java b/src/jade/math/GammaDistribution.java
new file mode 100644
index 0000000..627fc90
--- /dev/null
+++ b/src/jade/math/GammaDistribution.java
@@ -0,0 +1,102 @@
+/*
+ * GammaDistribution.java
+ *
+ * Created on August 9, 2005, 3:24 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+import java.util.*;
+/**
+ *
+ * @author stephensmith
+ */
+public class GammaDistribution implements ProbDistribution{
+
+ /** Creates a new instance of GammaDistribution */
+ public GammaDistribution(double shape, double scale){
+ this.shape = shape;
+ this.scale = scale;
+ norm = Math.log( scale ) * shape + GammaFunction.logGamma( shape );
+ if( shape < 1 )
+ b = (Math.E + shape)/Math.E;
+ else if( shape > 1){
+ a = Math.sqrt(2 * shape -1);
+ b = shape - Math.log(4.0);
+ q = shape + 1 /a;
+ d = 1+ Math.log(4.5);
+ }
+ }
+
+ public void setShape(double shape){this.shape = shape;}
+ public void setScale(double scale){this.scale = scale;}
+ public double getShape(){return shape;}
+ public double getScale(){return scale;}
+
+ public double getValue(){
+ double r;
+ if(shape >1)
+ r = randomForShapeGreaterThan1();
+ else if(shape < 1)
+ r = randomForShapeLessThan1();
+ else
+ r = randomForShapeEqualTo1();
+ return r * scale;
+ }
+
+ private double randomForShapeEqualTo1(){
+ return -Math.log( 1 - r.nextDouble());
+ }
+ private double randomForShapeGreaterThan1(){
+ double u1, u2, v, y, z, w;
+ while (true){
+ u1 = r.nextDouble();
+ u2 = r.nextDouble();
+ v = a*Math.log(u1/(1-u1));
+ y = shape * Math.exp(v);
+ z = u1*u1*u2;
+ w = b+q*v-y;
+ if(w+d-4.5*z>=0||w>=Math.log(z))
+ return y;
+
+ }
+ }
+ private double randomForShapeLessThan1(){
+ double p,y;
+ while(true){
+ p = r.nextDouble()*b;
+ if(p>1){
+ y = -Math.log((b-p)/shape);
+ if(r.nextDouble()<=Math.pow(y, shape-1))
+ return y;
+ }
+ y = Math.pow(p, 1/shape);
+ if(r.nextDouble()<=Math.exp(-y))
+ return y;
+ }
+ }
+
+ public double getPDF(double x){
+ return incompleteGammaFunction().value(x/scale);
+ }
+
+ private IncompleteGammaFunction incompleteGammaFunction(){
+ if(incompleteGammaFunction==null){
+ incompleteGammaFunction = new IncompleteGammaFunction(shape);
+ }
+ return incompleteGammaFunction;
+ }
+
+ //private methods
+ private double shape;
+ private double scale;
+ private double norm;
+ //private random number variables
+ private double a;
+ private double b;
+ private double q;
+ private double d;
+ private Random r = new Random();
+ private IncompleteGammaFunction incompleteGammaFunction;
+}
diff --git a/src/jade/math/GammaFunction.java b/src/jade/math/GammaFunction.java
new file mode 100644
index 0000000..7e9fac0
--- /dev/null
+++ b/src/jade/math/GammaFunction.java
@@ -0,0 +1,65 @@
+/*
+ * GammaFunction.java
+ *
+ * Created on August 9, 2005, 4:01 PM
+ *
+ * author: Stephen A. Smith
+ *
+ *
+ * ideas from Didier H. Besset
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public final class GammaFunction {
+
+ /** Creates a new instance of GammaFunction */
+ public GammaFunction() {
+ }
+ public static double beta( double x, double y ){
+ return Math.exp( logGamma( x ) + logGamma( y ) - logGamma( x + y ));
+ }
+ public static long factorial(long n){
+ return n<2 ? 1: n * factorial(n -1);
+ }
+ public static double gamma(double x){
+ return x > 1
+ ? Math.exp(leadingFactor(x)) * series(x) * sqrt2PI / x
+ : (x > 0 ? gamma(x+1)/x
+ : Double.NaN);
+ }
+ private static double leadingFactor(double x){
+ double temp = x+5.5;
+ return Math.log(temp)*(x+0.5)-temp;
+ }
+ public static double logBeta( double x, double y ){
+ return logGamma( x ) + logGamma( y ) - logGamma( x + y );
+ }
+ public static double logGamma(double x){
+ return x > 1
+ ? leadingFactor(x) + Math.log( series(x) * sqrt2PI/x)
+ : (x > 0 ? logGamma(x+1) - Math.log(x)
+ : Double.NaN);
+ }
+ private static double series(double x){
+ double answer = 1.000000000190015;
+ double term = x;
+ for(int i=0;i<6;i++){
+ term +=1;
+ answer+=coefficients[i]/term;
+ }
+ return answer;
+ }
+
+ static double sqrt2PI = Math.sqrt( 2 * Math.PI );
+ static double [] coefficients = { 76.18009172947146,
+ -86.505320329411677,
+ 24.01409824083091,
+ -1.231739572450155,
+ 0.1208650973866179e-2,
+ -0.5395239384953e-5};
+}
diff --git a/src/jade/math/IncompleteGammaFunction.java b/src/jade/math/IncompleteGammaFunction.java
new file mode 100644
index 0000000..24582db
--- /dev/null
+++ b/src/jade/math/IncompleteGammaFunction.java
@@ -0,0 +1,56 @@
+/*
+ * IncompleteGammaFunction.java
+ *
+ * Created on August 19, 2005, 11:08 AM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class IncompleteGammaFunction {
+
+ /** Creates a new instance of IncompleteGammaFunction */
+ public IncompleteGammaFunction(double alpha) {
+ this.alpha = alpha;
+ alphaLogGamma = GammaFunction.logGamma(alpha);
+ }
+
+ private double evaluateFraction(double x){
+ if(fraction==null){
+ fraction = new IncompleteGammaFunctionFraction(alpha);
+ fraction.setDesiredPrecision(MachinePrecision.defaultNumericalPrecision());
+ }
+ fraction.setArgument(x);
+ fraction.evaluate();
+ return fraction.getResult();
+ }
+
+ private double evaluateSeries(double x){
+ if(series==null){
+ series = new IncompleteGammaFunctionSeries(alpha);
+ series.setDesiredPrecision(MachinePrecision.defaultNumericalPrecision());
+ }
+ series.setArgument(x);
+ series.evaluate();
+ return series.getResult();
+ }
+
+ public double value(double x){
+ if(x==0)
+ return 0;
+ double norm = Math.exp(Math.log(x)*alpha - x - alphaLogGamma);
+ return x - 1 < alpha
+ ? evaluateSeries(x)*norm
+ : 1- norm / evaluateFraction(x);
+ }
+
+ private double alpha;
+ private double alphaLogGamma;
+ private IncompleteGammaFunctionSeries series;
+ private IncompleteGammaFunctionFraction fraction;
+}
diff --git a/src/jade/math/IncompleteGammaFunctionFraction.java b/src/jade/math/IncompleteGammaFunctionFraction.java
new file mode 100644
index 0000000..fe882fd
--- /dev/null
+++ b/src/jade/math/IncompleteGammaFunctionFraction.java
@@ -0,0 +1,36 @@
+/*
+ * IncompleteGammaFunctionFraction.java
+ *
+ * Created on August 19, 2005, 12:27 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class IncompleteGammaFunctionFraction extends ContinuedFraction{
+
+ /** Creates a new instance of IncompleteGammaFunctionFraction */
+ public IncompleteGammaFunctionFraction(double a) {
+ alpha = a;
+ }
+
+ protected void computeFactorsAt(int n){
+ sum+=2;
+ factors[0] = (alpha-n)*n;
+ factors[1] = sum;
+ return;
+ }
+
+ protected double initialValue(){
+ sum = x-alpha+1;
+ return sum;
+ }
+
+ private double alpha;
+ private double sum;
+}
diff --git a/src/jade/math/IncompleteGammaFunctionSeries.java b/src/jade/math/IncompleteGammaFunctionSeries.java
new file mode 100644
index 0000000..3df3c26
--- /dev/null
+++ b/src/jade/math/IncompleteGammaFunctionSeries.java
@@ -0,0 +1,36 @@
+/*
+ * IncompleteGammaFunctionSeries.java
+ *
+ * Created on August 19, 2005, 11:21 AM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class IncompleteGammaFunctionSeries extends InfiniteSeries{
+
+ /** Creates a new instance of IncompleteGammaFunctionSeries */
+ public IncompleteGammaFunctionSeries(double x) {
+ alpha = x;
+ }
+
+ protected void computeTermAt(int n){
+ sum += 1;
+ lastTerm *= x/sum;
+ return;
+ }
+
+ protected double initialValue(){
+ lastTerm = 1/alpha;
+ sum = alpha;
+ return lastTerm;
+ }
+
+ private double alpha;
+ private double sum;
+}
diff --git a/src/jade/math/InfiniteSeries.java b/src/jade/math/InfiniteSeries.java
new file mode 100644
index 0000000..c090131
--- /dev/null
+++ b/src/jade/math/InfiniteSeries.java
@@ -0,0 +1,42 @@
+/*
+ * InfiniteSeries.java
+ *
+ * Created on August 19, 2005, 11:24 AM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public abstract class InfiniteSeries extends IterativeProcess{
+
+ /** Creates a new instance of InfiniteSeries */
+ public InfiniteSeries() {
+ }
+
+ protected abstract void computeTermAt(int n);
+ public double evaluateIteration(){
+ computeTermAt(getIterations());
+ result+=lastTerm;
+ return relativePrecision(Math.abs(lastTerm), Math.abs(result));
+ }
+ public double getResult(){
+ return result;
+ }
+ public void initializeIterations(){
+ result = initialValue();
+ }
+ protected abstract double initialValue();
+ public void setArgument(double r){
+ x = r;
+ return;
+ }
+
+ private double result;
+ protected double x;
+ protected double lastTerm;
+}
diff --git a/src/jade/math/IterativeProcess.java b/src/jade/math/IterativeProcess.java
new file mode 100644
index 0000000..b84bdf4
--- /dev/null
+++ b/src/jade/math/IterativeProcess.java
@@ -0,0 +1,61 @@
+/*
+ * IterativeProcess.java
+ *
+ * Created on August 19, 2005, 11:30 AM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public abstract class IterativeProcess {
+
+ /** Creates a new instance of IterativeProcess */
+ public IterativeProcess() {
+ }
+
+ public void evaluate(){
+ iterations = 0;
+ initializeIterations();
+ while (iterations++<maximumIterations){
+ precision = evaluateIteration();
+ if(hasConverged())
+ break;
+ }
+ finalizeIterations();
+ }
+ abstract public double evaluateIteration();
+ public void finalizeIterations(){
+
+ }
+ public double getDesiredPrecision(){ return desiredPrecision; }
+ public int getIterations(){ return iterations; }
+ public int getMaximumIterations(){ return maximumIterations; }
+ public double getPrecision(){ return precision; }
+ public boolean hasConverged(){ return precision < desiredPrecision; }
+ public void initializeIterations(){ }
+ public double relativePrecision(double epsilon, double x){
+ return x > MachinePrecision.defaultNumericalPrecision()
+ ? epsilon / x:epsilon;
+ }
+ public void setDesiredPrecision(double prec) throws IllegalArgumentException{
+ if(prec<=0)
+ throw new IllegalArgumentException("non-positive precision: "+prec);
+ desiredPrecision = prec;
+ }
+ public void setMaximumIterations(int maxIter) throws IllegalArgumentException{
+ if(maxIter<=0)
+ throw new IllegalArgumentException("non-positive max iters: "+maxIter);
+ maximumIterations = maxIter;
+ }
+
+
+ private int iterations;
+ private int maximumIterations = 50;
+ private double desiredPrecision = MachinePrecision.defaultNumericalPrecision();
+ private double precision;
+}
diff --git a/src/jade/math/MachinePrecision.java b/src/jade/math/MachinePrecision.java
new file mode 100644
index 0000000..f1912e1
--- /dev/null
+++ b/src/jade/math/MachinePrecision.java
@@ -0,0 +1,141 @@
+/*
+ * MachinePrecision.java
+ *
+ * Created on August 19, 2005, 11:17 AM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public final class MachinePrecision {
+
+ private static void computeLargestNumber(){
+ double floatingRadix = getRadix();
+ double fullMantissaNumber = 1.0d - floatingRadix * getNegativeMachinePrecision();
+ while(!Double.isInfinite(fullMantissaNumber)){
+ largestNumber = fullMantissaNumber;
+ fullMantissaNumber *= floatingRadix;
+ }
+ }
+
+ private static void computeMachinePrecision(){
+ double floatingRadix = getRadix();
+ double inverseRadix = 1.0d/floatingRadix;
+ machinePrecision = 1.0d;
+ double tmp = 1.0d + machinePrecision;
+ while(tmp - 1.0d != 0.0d){
+ machinePrecision *= inverseRadix;
+ tmp= 1.0d+machinePrecision;
+ }
+ }
+
+ private static void computeNegativeMachinePrecision(){
+ double floatingRadix = getRadix();
+ double inverseRadix = 1.0d / floatingRadix;
+ negativeMachinePrecision = 1.0d;
+ double tmp = 1.0d + negativeMachinePrecision;
+ while(tmp - 1.0d != 0.0d){
+ negativeMachinePrecision *= inverseRadix;
+ tmp= 1.0d-negativeMachinePrecision;
+ }
+ }
+
+ private static void computeRadix(){
+ double a = 1.0d;
+ double tmp1,tmp2;
+ do{ a +=a;
+ tmp1 = a +1.0d;
+ tmp2 = tmp1 -a;
+ }while(tmp2-1.0d!=0.0d);
+ double b = 1.0d;
+ while(radix==0){
+ b+=b;
+ tmp1 = a+b;
+ radix = (int)(tmp1-a);
+ }
+ }
+
+ private static void computeSmallestNumber(){
+ double floatingRadix = getRadix();
+ double inverseRadix = 1.0d/ floatingRadix;
+ double fullMantissaNumber = 1.0d - floatingRadix * getNegativeMachinePrecision();
+ while(fullMantissaNumber!=0.0d){
+ smallestNumber = fullMantissaNumber;
+ fullMantissaNumber *= inverseRadix;
+ }
+ }
+
+ public static double defaultNumericalPrecision(){
+ if(defaultNumericalPrecision == 0){
+ defaultNumericalPrecision = Math.sqrt(getMachinePrecision());
+ }return defaultNumericalPrecision;
+ }
+
+ public static boolean equal(double a, double b){
+ return equal(a, b, defaultNumericalPrecision());
+ }
+
+ public static boolean equal(double a, double b, double prec){
+ double norm = Math.max(Math.abs(a), Math.abs(b));
+ return norm < prec || Math.abs(a-b) < prec *norm;
+ }
+
+ public static double getLargestExponentialArgument(){
+ if(largestExponentialArgument == 0)
+ largestExponentialArgument = Math.log(getLargestNumber());
+ return largestExponentialArgument;
+ }
+
+ public static double getLargestNumber(){
+ if(largestNumber == 0)
+ computeLargestNumber();
+ return largestNumber;
+ }
+
+ public static double getMachinePrecision(){
+ if(machinePrecision == 0)
+ computeMachinePrecision();
+ return machinePrecision;
+ }
+
+ public static double getNegativeMachinePrecision(){
+ if(negativeMachinePrecision == 0)
+ computeNegativeMachinePrecision();
+ return negativeMachinePrecision;
+ }
+
+ public static int getRadix(){
+ if(radix==0)
+ computeRadix();
+ return radix;
+ }
+
+ public static double getSmallestNumber(){
+ if(smallestNumber == 0)
+ computeSmallestNumber();
+ return smallestNumber;
+ }
+
+ public static double smallNumber(){
+ if(smallNumber==0)
+ smallNumber = Math.sqrt(getSmallestNumber());
+ return smallNumber;
+ }
+
+ static private double defaultNumericalPrecision = 0;
+ static private double smallNumber = 0;
+ static private int radix = 0;
+ static private double machinePrecision = 0;
+ static private double negativeMachinePrecision = 0;
+ static private double smallestNumber = 0;
+ static private double largestNumber = 0;
+ static private double largestExponentialArgument = 0;
+ private static final double scales [] = {1.25, 2, 2.5, 4, 5, 7.5, 8, 10};
+ private static final double semiIntegerScales[] = {2, 2.5, 4, 5, 7.5, 8, 10};
+ private static final double integerScales[] = {2,4,5,8,10};
+}
diff --git a/src/jade/math/NChooseM.java b/src/jade/math/NChooseM.java
new file mode 100644
index 0000000..2353b52
--- /dev/null
+++ b/src/jade/math/NChooseM.java
@@ -0,0 +1,195 @@
+/*
+ * NChooseM.java
+ *
+ * Created on June 26, 2005, 12:19 PM
+ *
+ * Stephen Smith
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class NChooseM {
+
+ /** Creates a new instance of NChooseM */
+ public NChooseM() {
+ }
+ public static int combinations(int m, int n){
+ if(!(m >= n)&&!(n >= 0))
+ System.out.println("m >= n >= 0 required");
+ if (n > (m >> 1))
+ n = m-n;
+ if (n == 0)
+ return 1;
+ int result = m;
+ int i=2;
+ m=m-1;n=n-1;
+ while(n>0){
+ //assert (result * m) % i == 0
+ result = result * m / i;
+ i = i+1;
+ n = n-1;
+ m = m-1;
+ }
+ return result;
+ }
+ public static int [] combinationsAtIndex(int m, int n, double i){
+ if(!(m >= n)&&!(n >= 1))
+ System.out.println("m >= n >= 1 required");
+ double c=combinations(m,n);
+ if(!(0 <= i)&&!(i < c))
+ System.out.println("0 <= i < comb(m,n) required");
+ int [] result = new int [0];
+ c = c * n / m;
+ for(int j=0;j<=m;j++){
+ if (i < c){
+ result = Utils.addToArray(result,j);
+ n = n-1;
+ if (n == 0)
+ break;
+ c = c * n / (m-1);
+ }
+ else{
+ i = i-c;
+ c = c * (m-n) / (m-1);
+ }
+ m = m-1;
+ }
+ //assert i == 0
+ return result;
+ }
+ public static int [][] iterate(int M, int N){
+ if(!(M >= N)&&!(N >= 1))
+ System.out.println("M >= N >= 1 required");
+ int ncombs = combinations(M,N);
+ int [][] result = new int[ncombs][0];
+ for(int x=0;x<ncombs;x++){
+ int i = x; int n = N; int m = M;
+ int c = ncombs * n / m;
+ int element=0;
+ while(m>0){
+ //System.out.println(element+" "+i+" "+c+" "+m+" "+n);
+ if (i < c){
+ result[x] = Utils.addToArray(result[x], element);
+ n = n-1;
+ if (n == 0)
+ break;
+ c = c*n/(m-1);
+ }
+ else{
+ i = i-c;
+ c = c*(m-n)/(m-1);
+ }
+ element++;
+ m = m-1;
+ }
+ }
+ return result;
+
+ }
+ public static int [][] idx2bitvect(int [][] indices, int M){
+ int [][] v = new int [indices.length][M];
+ for(int i=0;i<v.length;i++){
+ for(int j=0;j<v[i].length;j++){
+ v[i][j]=0;
+ }
+ }
+ for(int i=0;i<indices.length;i++){
+ for(int j=0;j<indices[i].length;j++){
+ v[i][indices[i][j]]=1;
+ }
+ }
+ return v;
+ }
+ public static int [][][] iterate_all_bv2(int m){
+ int [][][] y = new int [m*m][m][m];
+ int [][][] it = new int [m*m][0][0];
+ for(int n=1;n<m+1;n++){
+ it[n-1]=iterate(m,n);
+ }
+ for(int w=0;w<it.length;w++){
+ y[w]=idx2bitvect(it[w], m);
+ }
+ int [][][] tempA = new int [m*m+1][1][m];
+ for(int w=0;w<y.length;w++){
+ tempA[w+1]=y[w];
+ }
+ y=tempA;
+ for(int z=0;z<m;z++){
+ for(int x=0;x<m;x++){
+ y[0][0][x]=0;
+ }
+ }
+ return y;
+ }
+ public static int [][] iterate_all_bv2small(int m){
+ int mm = 1;
+ for(int n=0;n<m;n++){
+ mm= mm*2;
+ }
+ int [][][] y = new int [mm][m][m];
+ int [][][] it = new int [mm][0][0];
+ for(int n=1;n<m+1;n++){
+ it[n-1]=iterate(m,n);
+ }
+ for(int w=0;w<it.length;w++){
+ y[w]=idx2bitvect(it[w], m);
+ }
+ int [][][] tempA = new int [mm+1][1][m];
+ for(int w=0;w<y.length;w++){
+ tempA[w+1]=y[w];
+ }
+ y=tempA;
+ for(int z=0;z<m;z++){
+ for(int x=0;x<m;x++){
+ y[0][0][x]=0;
+ }
+ }
+ int [][] smally = new int [mm][m];
+ int q=0;
+ for(int w=0;w<y.length;w++){
+ for(int j=0;j<y[w].length;j++){
+ int r = 0;
+ for(int k=0;k<y[w][j].length;k++){
+ smally[q][r]=y[w][j][k];
+ r++;
+ }
+ q++;
+ }
+ }
+ //delete first row
+ int [][] TsmallY = new int [mm-1][m];
+ q=0;
+ for(int i=1;i<smally.length;i++){
+ TsmallY[q]=smally[i];
+ q++;
+ }
+ smally=TsmallY;
+ //end delete
+ return smally;
+ }
+ public static void main(String[] args){
+ int m=4;int n=2;
+ int [][] y = iterate_all_bv2small(m);
+ for(int w=0;w<y.length;w++){
+ for(int j=0;j<y[w].length;j++){
+ System.out.print(y[w][j]);
+ }
+ System.out.println();
+ }
+ System.out.println("done");
+ System.out.println((3*3*2-1)+" "+y.length);
+
+// y = iterate(80,3);
+// for(int w=0;w<y.length;w++){
+// for(int j=0;j<y[w].length;j++){
+// System.out.print(y[w][j]);
+// }
+// System.out.println();
+// }
+ }
+}
+
diff --git a/src/jade/math/NormalDistribution.java b/src/jade/math/NormalDistribution.java
new file mode 100644
index 0000000..8b7e06e
--- /dev/null
+++ b/src/jade/math/NormalDistribution.java
@@ -0,0 +1,44 @@
+/*
+ * NormalDistribution.java
+ *
+ * Created on August 9, 2005, 3:24 PM
+ *
+ * author: Stephen A. Smith
+ */
+
+package jade.math;
+
+import java.util.*;
+/**
+ *
+ * @author stephensmith
+ */
+public class NormalDistribution implements ProbDistribution{
+
+ /** Creates a new instance of NormalDistribution */
+ public NormalDistribution() {
+ }
+
+ public NormalDistribution(double mean, double stdev){
+ this.mean = mean;
+ this.stdev = stdev;
+ }
+
+ public void setStDev(double stdev){ this.stdev = stdev; }
+
+ public void setMean(double mean){ this.mean = mean; }
+
+ public double getValue(){
+ return (r.nextGaussian()*stdev)+mean;
+ }
+
+ public double getPDF(double x){
+ //add error function
+ return (x-mean)/stdev;
+ }
+
+ //private methods
+ private double stdev;
+ private double mean;
+ private Random r = new Random();
+}
diff --git a/src/jade/math/PrecisionCalculator.java b/src/jade/math/PrecisionCalculator.java
new file mode 100644
index 0000000..9ebbb11
--- /dev/null
+++ b/src/jade/math/PrecisionCalculator.java
@@ -0,0 +1,57 @@
+package jade.math;
+
+public final class PrecisionCalculator {
+
+ /** Radix used by floating-point numbers. */
+ private final static int radix = computeRadix();
+
+ /** Largest positive value which, when added to 1.0, yields 0 */
+ private final static double machinePrecision = computeMachinePrecision();
+
+ /** Typical meaningful precision for numerical calculations. */
+ private final static double defaultNumericalPrecision = Math
+ .sqrt(machinePrecision);
+
+ private static int computeRadix() {
+ int radix = 0;
+ double a = 1.0d;
+ double tmp1, tmp2;
+ do {
+ a += a;
+ tmp1 = a + 1.0d;
+ tmp2 = tmp1 - a;
+ } while (tmp2 - 1.0d != 0.0d);
+ double b = 1.0d;
+ while (radix == 0) {
+ b += b;
+ tmp1 = a + b;
+ radix = (int) (tmp1 - a);
+ }
+ return radix;
+ }
+
+ private static double computeMachinePrecision() {
+ double floatingRadix = getRadix();
+ double inverseRadix = 1.0d / floatingRadix;
+ double machinePrecision = 1.0d;
+ double tmp = 1.0d + machinePrecision;
+ while (tmp - 1.0d != 0.0d) {
+ machinePrecision *= inverseRadix;
+ tmp = 1.0d + machinePrecision;
+ }
+ return machinePrecision;
+ }
+
+ public static int getRadix() {
+ return radix;
+ }
+
+ public static double getMachinePrecision() {
+ return machinePrecision;
+ }
+
+ public static double defaultNumericalPrecision() {
+ return defaultNumericalPrecision;
+ }
+
+}
diff --git a/src/jade/math/ProbDistribution.java b/src/jade/math/ProbDistribution.java
new file mode 100644
index 0000000..d9a7fff
--- /dev/null
+++ b/src/jade/math/ProbDistribution.java
@@ -0,0 +1,27 @@
+/*
+ * ProbDistribution.java
+ *
+ * Created on August 9, 2005, 3:09 PM
+ *
+ * To change this template, choose Tools | Options and locate the template under
+ * the Source Creation and Management node. Right-click the template and choose
+ * Open. You can then make changes to the template in the Source Editor.
+ */
+
+package jade.math;
+
+/**
+ *
+ * @author stephensmith
+ */
+public interface ProbDistribution {
+ /**
+ *@return double value
+ */
+ public double getValue();
+
+ /**
+ *@return the integral of the probability density function from 0 to x
+ */
+ public double getPDF(double x);
+}
diff --git a/src/jade/math/Utils.java b/src/jade/math/Utils.java
new file mode 100644
index 0000000..4101c2a
--- /dev/null
+++ b/src/jade/math/Utils.java
@@ -0,0 +1,393 @@
+/*
+ * Utils.java
+ *
+ * Created on June 27, 2005, 2:02 PM
+ *
+ * This is just a reserve for utilities
+ */
+
+package jade.math;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+ *
+ * @author stephensmith
+ */
+public class Utils {
+
+ /** Creates a new instance of Utils */
+ // public Utils() {}
+ public static double conditional_exp(double t, double lambd) {
+ Random r = new Random();
+ double U = r.nextDouble();
+ double p = -(1.0 / lambd)
+ * Math.log(1.0 - U * (1.0 - Math.exp(-lambd * t)));
+ return p;
+ }
+
+ /*
+ * public static Tree calibrate_tree(Tree intree, double cal){ Tree tempT =
+ * intree.getCopy(); double len2tip = 0.0; boolean t = true; Node tempN =
+ * tempT.getRoot(); len2tip = tempN.getNodeHeight();
+ *
+ * double scale = cal/len2tip; System.out.println(scale); for(int i=0;i<tempT.getInternalNodeCount();i++){
+ * tempN=tempT.getInternalNode(i); double value = tempN.getBranchLength() *
+ * scale; if(tempN != tempT.getRoot()){ tempN.setBranchLength(value); }else
+ * tempN.setBranchLength(0.0); } for(int i=0;i<tempT.getExternalNodeCount();i++){
+ * tempN=tempT.getExternalNode(i); double value = tempN.getBranchLength() *
+ * scale; tempN.setBranchLength(value); } return tempT; } public static void
+ * printTree(Tree intree){ TreeUtils tu = new TreeUtils(); PrintWriter out =
+ * new PrintWriter(System.out); tu.report(intree, out); out.flush(); }
+ */
+ // add to array functions
+ public static int[][][] combineITER(int[][][] inA, int[][][] inB) {
+ int[][][] retA = new int[inA.length + inB.length][][];
+ int x = 0;
+ for (int i = 0; i < inA.length; i++) {
+ retA[x] = inA[i];
+ x++;
+ }
+ for (int i = 0; i < inB.length; i++) {
+ retA[x] = inB[i];
+ x++;
+ }
+ return retA;
+ }
+
+ public static int[][][] addToArray(int[][][] inA, int[] leftin,
+ int[] rightin) {
+ int[][][] tempA = new int[inA.length + 1][2][];
+ for (int i = 0; i < inA.length; i++) {
+ tempA[i] = inA[i];
+ }
+ tempA[inA.length][0] = leftin;
+ tempA[inA.length][1] = rightin;
+ inA = tempA;
+ return inA;
+ }
+
+ public static String[] addToArray(String[] inArray, String inelement) {
+ String[] tempA = new String[inArray.length + 1];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ public static int[] addToArray(int[] inArray, int inelement) {
+ int[] tempA = new int[inArray.length + 1];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ public static double[] addToArray(double[] inArray, double inelement) {
+ double[] tempA = new double[inArray.length + 1];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ public static double[][] addToArray(double[][] inArray, double[] inelement) {
+ double[][] tempA = new double[inArray.length + 1][];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ public static int[][][] addToArray(int[][][] inArray, int[][] inelement) {
+ int[][][] tempA = new int[inArray.length + 1][][];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ public static int[][] addToArray(int[][] inArray, int[] inelement) {
+ int[][] tempA = new int[inArray.length + 1][];
+ for (int i = 0; i < inArray.length; i++) {
+ tempA[i] = inArray[i];
+ }
+ tempA[inArray.length] = inelement;
+ inArray = tempA;
+ return inArray;
+ }
+
+ // linear algebra functions
+ public static double[] divide(double[] ina, int num) {
+ for (int i = 0; i < ina.length; i++) {
+ ina[i] = ina[i] / num;
+ }
+ return ina;
+ }
+ public static double[][] multiply_mat_dou(double[][] a, double [][] b) {
+ double [][] ret = new double [a.length][a[0].length];
+ for (int i = 0; i < a.length; i++) {
+ for (int j = 0; j < a[i].length; j++) {
+ ret [i][j] = a[i][j] * b[i][j];
+ }
+ }
+ return ret;
+ }
+ public static int sum(int[] ina) {
+ int sum = 0;
+ for (int i = 0; i < ina.length; i++) {
+ sum = sum + ina[i];
+ }
+ return sum;
+ }
+
+ public static double sum(double[] inA) {
+ double tempD = 0;
+ for (int i = 0; i < inA.length; i++) {
+ tempD = tempD + inA[i];
+ // System.out.println(inA[i]);
+ }
+ return tempD;
+ }
+
+ public static int[][][] iter_splitranges(int nareas) {
+ int[][][] retA = new int[0][][];
+ int[][] distributions = NChooseM.iterate_all_bv2small(nareas);
+ for (int i = 0; i < distributions.length; i++) {
+ retA = Utils.combineITER(retA, iter_splitranges(distributions[i]));
+ }
+ // for(int i=0;i<retA.length;i++){
+ // for(int j=0;j<retA[i].length;j++){
+ // for(int k=0;k<retA[i][j].length;k++){
+ // System.out.print(retA[i][j][k]);
+ // }System.out.print(" ");
+ // }System.out.println();
+ // }
+ return retA;
+ }
+
+ public static int[][][] iter_splitranges(int[] rng) {
+ int[][][] retA = new int[0][0][0];
+ int numM = 0;
+ for (int i = 0; i < rng.length; i++) {
+ if (rng[i] == 1) {
+ numM++;
+ }
+ }
+ retA = new int[numM][2][];
+ if (numM == 1) {
+ retA[0][0] = rng;
+ retA[0][1] = rng;
+ // added for dispersal
+ // int [][][] retB = iter_splitranges_dispersal(rng);
+ // retA = Utils.combineITER(retA, retB);
+ // end added
+ } else {
+ int[][][] retB = iter_splitranges_allo(rng);
+ int[][][] retC = iter_splitranges_sym(rng);
+ retA = Utils.combineITER(retB, retC);
+ // added for dispersal
+ // int [][][] retD = iter_splitranges_dispersal(rng);
+ // retA = Utils.combineITER(retA, retD);
+ // end added
+ }
+ return retA;
+ }
+
+ private static int[][][] iter_splitranges_allo(int[] rng) {
+ int[][][] retA = new int[0][][];
+ int numofones = 0;
+ for (int i = 0; i < rng.length; i++) {
+ if (rng[i] == 1)
+ numofones++;
+ }
+ if (numofones == 2) {
+ for (int i = 0; i < rng.length; i++) {
+ int[] tempLA = new int[rng.length];
+ int[] tempRA = new int[rng.length];
+ if (rng[i] == 1) {
+ for (int j = 0; j < rng.length; j++) {
+ tempLA[j] = 0;
+ tempRA[j] = rng[j];
+ if (j == i) {
+ tempLA[i] = 1;
+ tempRA[i] = 0;
+ }
+ }
+ retA = Utils.addToArray(retA, tempLA, tempRA);
+ }
+ }
+ } else {
+ for (int i = 0; i < rng.length; i++) {
+ int[] tempLA = new int[rng.length];
+ int[] tempRA = new int[rng.length];
+ if (rng[i] == 1) {
+ for (int j = 0; j < rng.length; j++) {
+ tempLA[j] = 0;
+ tempRA[j] = rng[j];
+ if (j == i) {
+ tempLA[i] = 1;
+ tempRA[i] = 0;
+ }
+ }
+ retA = Utils.addToArray(retA, tempLA, tempRA);
+ retA = Utils.addToArray(retA, tempRA, tempLA);
+ }
+ }
+ }
+ return retA;
+ }
+
+ private static int[][][] iter_splitranges_sym(int[] rng) {
+ int[][][] retA = new int[0][][];
+ for (int i = 0; i < rng.length; i++) {
+ int[] tempLA = new int[rng.length];
+ int[] tempRA = new int[rng.length];
+ if (rng[i] == 1) {
+ for (int j = 0; j < rng.length; j++) {
+ tempLA[j] = 0;
+ tempRA[j] = rng[j];
+ if (j == i) {
+ tempLA[i] = 1;
+ tempRA[i] = 1;
+ }
+ }
+ retA = Utils.addToArray(retA, tempLA, tempRA);
+ retA = Utils.addToArray(retA, tempRA, tempLA);
+ }
+ }
+ return retA;
+ }
+
+ private static int[][][] iter_splitranges_dispersal(int[] rng) {
+ int numofzeros = 0;
+ for (int i = 0; i < rng.length; i++) {
+ if (rng[i] == 0)
+ numofzeros++;
+ }
+ int[][][] retA = new int[0][][];
+ if (numofzeros > 0) {
+ for (int i = 0; i < rng.length; i++) {
+ int[] tempLA = new int[rng.length];
+ int[] tempRA = new int[rng.length];
+ if (rng[i] == 0) {
+ tempRA = rng;
+ for (int j = 0; j < rng.length; j++) {
+ tempLA[j] = 0;
+ if (j == i) {
+ tempLA[j] = 1;
+ }
+ }
+ retA = Utils.addToArray(retA, tempLA, tempRA);
+ retA = Utils.addToArray(retA, tempRA, tempLA);
+ }
+ }
+ }
+ return retA;
+ }
+
+ /*
+ * this will return a vector of two with the first result being whether
+ * there is a dispersal from one distribution to the second and the second
+ * result being whether there is an extinction a null result means that
+ * there is too much a [0,0] results means that it is the same distribution
+ *
+ */
+ public static int[] getDistanceBtwDistributions(int[] dist1, int[] dist2) {
+ int[] retV = new int[2];// 0 == dispersals, 1 == extinctions
+ for (int i = 0; i < 2; i++) {
+ retV[i] = 0;
+ }
+ for (int i = 0; i < dist1.length; i++) {
+ if (dist1[i] == 0 && dist2[i] == 1) {
+ retV[0]++;
+ } else if (dist1[i] == 1 && dist2[i] == 0) {
+ retV[1]++;
+ }
+ }
+ if (retV[0] > 1 || retV[1] > 1)
+ return null;
+ if (retV[0] > 0 && retV[1] > 0)
+ return null;
+ return retV;
+ }
+
+ public static String printCombIVec(int [] vec1, int [] vec2){
+ String retS = "";
+ for (int i = 0; i < vec1.length; i++) {
+ if(vec1[i] ==1 || vec2[i]==1)
+ retS += String.valueOf(1);
+ else
+ retS += String.valueOf(0);
+ }
+ return retS;
+ }
+
+ public static String printIVec(int[] vec) {
+ String retS = "";
+ for (int i = 0; i < vec.length; i++) {
+ retS += String.valueOf(vec[i]);
+ }
+ return retS;
+ }
+
+ public static String printDVec(double[] vec) {
+ String retS = "";
+ for (int i = 0; i < vec.length; i++) {
+ retS += String.valueOf(vec[i])+"\t";
+ }
+ return retS;
+ }
+
+ public static String printDMat(double [][] m) {
+ String retS = "";
+ for (int i = 0; i < m.length; i++) {
+ for (int j = 0; j < m[i].length; j++) {
+ retS += String.valueOf(m[i][j]+"\t");
+ }retS += "\n";
+ }
+ return retS;
+ }
+
+ /*
+ * logical xor
+ * returns an array the length of the input arrays
+ * which has true or false corresonding to NON-matching
+ * parts
+ */
+ public static int [] logical_xor_int(int [] a, int [] b ){
+ assert a.length == b.length;
+ int [] ret = new int [a.length];
+ for(int i=0;i<a.length;i++){
+ if(a[i]==b[i])
+ ret[i] = 0;//false
+ else
+ ret[i] = 1;//true
+ }
+ return ret;
+ }
+
+ /*
+ * returns an arraylist of the position nonzero elements
+ */
+ public static ArrayList<Integer> nonzero_int(int [] a){
+ ArrayList<Integer> ret = new ArrayList<Integer>();
+ for(int i=0;i<a.length;i++){
+ if(a[i] != 0)
+ ret.add(i);
+ }
+ return ret;
+ }
+}
diff --git a/src/jade/math/fRan.java b/src/jade/math/fRan.java
new file mode 100644
index 0000000..ee3c2af
--- /dev/null
+++ b/src/jade/math/fRan.java
@@ -0,0 +1,57 @@
+package jade.math;
+
+import java.util.Random;
+
+public class fRan {
+ public static double fRan(double d){
+ int RAN_IA = 16807;
+ int RAN_IM = 2147483647;
+ double RAN_AM = (1.0/RAN_IM);
+ int RAN_IQ = 127773;
+ int RAN_IR = 2836;
+ int RAN_NTAB =32;
+ int RAN_NDIV =(1+(RAN_IM-1)/RAN_NTAB);
+ double RAN_EPS = 1.2e-7;
+ double RAN_RNMX =(1.0-RAN_EPS);
+ long seed = (new Random()).nextLong();
+
+ double temp;
+ int j;
+ long k;
+ long iy = 0;
+ long [] iv = new long[RAN_NTAB];
+
+ if (seed <= 0 || iy == 0) {
+ if (-(seed) < 1)
+ seed = 1;
+ else
+ seed = -(seed);
+ for (j = RAN_NTAB+7; j >= 0; j--) {
+ k = (seed)/RAN_IQ;
+ seed = RAN_IA*(seed-k*RAN_IQ)-RAN_IR*k;
+ if (seed < 0)
+ seed += RAN_IM;
+ if (j < RAN_NTAB)
+ iv[j] = seed;
+ }
+ iy = iv[0];
+ }
+ k = (seed)/RAN_IQ;
+ seed = RAN_IA*(seed-k*RAN_IQ)-RAN_IR*k;
+ if (seed < 0)
+ seed += RAN_IM;
+ j = (int)iy/RAN_NDIV;//(int)
+ iy = iv[j];
+ iv[j] = seed;
+ if ((temp=RAN_AM*iy) > RAN_RNMX)
+ return d*RAN_RNMX;
+ else
+ return d*temp;
+ }
+ public static void main(String [] args){
+ for(int i=0;i<1000;i++){
+ System.out.println((fRan.fRan(0.1)));
+ }
+ }
+
+}
diff --git a/src/jade/reconstruct/area/AncSplit.java b/src/jade/reconstruct/area/AncSplit.java
new file mode 100644
index 0000000..5099cdc
--- /dev/null
+++ b/src/jade/reconstruct/area/AncSplit.java
@@ -0,0 +1,20 @@
+package jade.reconstruct.area;
+
+public class AncSplit {
+ public AncSplit(int [] ancdists, int [][] descdists, double weight, double likelihood){
+ this.weight = weight;
+ this.likelihood = likelihood;
+ this.ancdist = ancdists;
+ this.descdists = descdists;
+ }
+ private double weight;
+ private double likelihood;
+ private int [][] descdists;
+ private int [] ancdist;
+
+ public double get_weight(){return weight;}
+ public double get_like(){return likelihood;}
+ public int [] get_ancdist(){return ancdist;}
+ public int [][] get_descdists(){return descdists;}
+
+}
diff --git a/src/jade/reconstruct/area/Area.java b/src/jade/reconstruct/area/Area.java
new file mode 100644
index 0000000..bf396c3
--- /dev/null
+++ b/src/jade/reconstruct/area/Area.java
@@ -0,0 +1,6 @@
+package jade.reconstruct.area;
+
+public class Area {
+ private int number;
+ private String name;
+}
diff --git a/src/jade/reconstruct/area/BayesChain.java b/src/jade/reconstruct/area/BayesChain.java
new file mode 100644
index 0000000..66516cc
--- /dev/null
+++ b/src/jade/reconstruct/area/BayesChain.java
@@ -0,0 +1,22 @@
+package jade.reconstruct.area;
+
+import java.util.*;
+
+public class BayesChain {
+ public BayesChain(double disp_sliding_window,double ext_sliding_window){
+ this.disp_sliding_window = disp_sliding_window;
+ this.ext_sliding_window = ext_sliding_window;
+ }
+ public double score;
+ public double vag;
+ public double ext;
+ public double ext_sliding_window;
+ public double disp_sliding_window;
+ public double next_ext(double cur){
+ return Math.abs(cur -(ext_sliding_window/2)+(ext_sliding_window*ran.nextDouble()));
+ }
+ public double next_disp(double cur){
+ return Math.abs(cur -(disp_sliding_window/2)+(disp_sliding_window*ran.nextDouble()));
+ }
+ private Random ran = new Random();
+}
diff --git a/src/jade/reconstruct/area/BayesianMarginalRangeReconstructor.java b/src/jade/reconstruct/area/BayesianMarginalRangeReconstructor.java
new file mode 100644
index 0000000..dfe3eae
--- /dev/null
+++ b/src/jade/reconstruct/area/BayesianMarginalRangeReconstructor.java
@@ -0,0 +1,272 @@
+package jade.reconstruct.area;
+
+import jade.tree.*;
+import jade.data.*;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.*;
+
+public class BayesianMarginalRangeReconstructor{
+ String LTGN = "likelihood_of_tree_given_n";
+ String MAPLTGN = "map_of_likelihood_of_tree_given_n";
+ private RateModel ratemodel;
+ //int decimalPlace = 18;
+ private MarginalRangeReconstructor rr;
+ private ArrayList<Node> report_nodes;
+ private String alnfile = "";
+ private String treestring = "(((1:0.35790777972472654,2:0.35790777972472654)1111:24.81884979583625,3:25.176757575560977)0100:21.651674361662142,((((((4:3.2792789357060386,((5:1.2043992449029162,6:1.2043992449029162)1000:0.2327030710104765,7:1.4371023159133927)1111:1.842176619792646)1101:8.335942634266438,8:11.615221569972476)1000:3.3886557145980234,9:15.0038772845705)1000:4.94912402195925,(10:13.15511790777304,(11:8.946146423606933,(12:4.2155815898511975,13:4.2155815898511975)0110:4.730 [...]
+ private Tree tree;
+ private Alignment aln;
+ private double disp_sliding_window = 0.23;
+ private double ext_sliding_window = 0.23;
+ private int print_freq = 1000;
+ private int report_freq = 100;
+ private boolean to_file = true;
+ private String outfile = "/home/smitty/scratch/bayes_test.txt";
+ private String dir = "/home/smitty/scratch/";
+
+ public BayesianMarginalRangeReconstructor(RateModel ratemodel, Tree tree,
+ Alignment aln) {
+ this.ratemodel = ratemodel;
+ this.aln = aln;
+ this.tree = tree;
+ report_nodes = new ArrayList<Node>();
+ //report_nodes.add(tree.getRoot());
+ //report_nodes.add(tree.getInternalNode("M"));
+ //report_nodes.add(tree.getInternalNode("X"));
+ //report_nodes.add(tree.getInternalNode("Y"));
+ //report_nodes.add(tree.getInternalNode("Z"));
+ }
+
+ public void start_run(int n, int nchains){
+ File outf = new File(outfile);
+ outf.delete();
+ try {
+ FileWriter fw = new FileWriter(outfile, true);
+ fw.write("rep"+"\t"+"disp"+"\t"+"ext"+"\t"+"score"+"\n");
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ /*
+ * initiate files for report nodes
+ */
+ for(int i=0;i<report_nodes.size();i++){
+ outf = new File(dir+report_nodes.get(i).getName());
+ outf.delete();
+ }
+ /*
+ *
+ */
+ Random ran = new Random();
+ ArrayList<BayesChain> chains = new ArrayList<BayesChain>();
+ for(int i=0;i<nchains;i++){
+ if(nchains > 1 && i+1 != nchains){
+ BayesChain chain = new BayesChain(0.5,1);
+ chains.add(chain);
+ }else{
+ BayesChain chain = new BayesChain(0.01,0.01);
+ chains.add(chain);
+ }
+ }
+ /*
+ * priors
+ */
+ jade.math.ExponentialDistribution disp = new jade.math.ExponentialDistribution();
+ jade.math.ExponentialDistribution ext = new jade.math.ExponentialDistribution();
+ disp.setFallOff(0.5);
+ ext.setFallOff(1);
+ /*
+ * start the initial chain
+ */
+ double cur_disp = 0.1;
+ double cur_ext = 1.1;
+ rr = new MarginalRangeReconstructor(ratemodel, tree, aln);
+ rr.set_dipsersal(cur_disp);
+ rr.set_extinction(cur_ext);
+ rr.set_ratemodel();
+ double cur_score = Math.log(rr.eval_likelihood());
+ for(int i=0;i<n;i++){//numofreps
+ int best = 0;
+ double bsc = 99999999;
+ for(int j=0;j<chains.size();j++){
+ chains.get(j).vag = chains.get(j).next_disp(cur_disp);
+ chains.get(j).ext = chains.get(j).next_disp(cur_ext);
+ rr = new MarginalRangeReconstructor(ratemodel, tree, aln);
+ rr.set_dipsersal(chains.get(j).vag);
+ rr.set_extinction(chains.get(j).ext);
+ rr.set_ratemodel();
+ chains.get(j).score = Math.log(rr.eval_likelihood());
+ if(-chains.get(j).score<bsc){
+ best = j;
+ bsc = -chains.get(j).score;
+ }
+ }
+ double new_score =chains.get(best).score;
+ /*
+ * accept or reject
+ */
+ boolean acc = false;
+ double disp_prior_ratio = disp.getPDF(chains.get(best).vag)/disp.getPDF(cur_disp);
+ double ext_prior_ratio = ext.getPDF(chains.get(best).ext)/ext.getPDF(cur_ext);
+ double like_ratio = new_score/cur_score;
+ double dou_acc = disp_prior_ratio*ext_prior_ratio*like_ratio;
+ double min_acc = Math.min(1, dou_acc);
+ if(ran.nextDouble()<min_acc){
+ acc = true;
+ }
+ if(acc == true){
+ cur_disp = chains.get(best).vag;
+ cur_ext = chains.get(best).ext;
+ cur_score = new_score;
+ }else{
+ chains.get(best).vag = cur_disp;
+ chains.get(best).ext = cur_ext;
+ chains.get(best).score = cur_score;
+ }
+ if((i%print_freq) == 0){
+ System.out.print(i+"\t"+cur_disp+"\t"+cur_ext+"\t");
+ for(int j=0;j<chains.size();j++){
+ if(j==best)
+ System.out.print("[");
+ System.out.print(chains.get(j).score);
+ if(j==best)
+ System.out.print("]");
+ System.out.print("\t");
+ }
+ System.out.println();
+ }
+ if((i%report_freq) == 0 && to_file == true){
+ try {
+ FileWriter fw = new FileWriter(outfile, true);
+ fw.write(i+"\t"+cur_disp+"\t"+cur_ext+"\t"+cur_score+"\n");
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ /*
+ * report nodes
+ */
+ if(report_nodes.size()>0){
+ rr = new MarginalRangeReconstructor(ratemodel, tree, aln);
+ rr.set_dipsersal(chains.get(best).vag);
+ rr.set_extinction(chains.get(best).ext);
+ rr.set_ratemodel();
+ rr.eval_likelihood();
+ rr.calc_internal_likelihoods_all_ranges(false);
+ for(int j=0;j<report_nodes.size();j++){
+ try {
+ if(i == 0){
+ FileWriter fw = new FileWriter(dir+report_nodes.get(j).getName(), true);
+ for(int m=0;m<ratemodel.get_dists().length;m++){
+ int [][][] iters = rr.iter_dist_splits_weighted(ratemodel.get_dists()[m]);
+ for(int k=0;k<iters.length;k++){
+ String pri = "";
+ int [] d1 = iters[k][0];
+ int [] d2 = iters[k][1];
+ pri += jade.math.Utils.printIVec(d1)+"_"+jade.math.Utils.printIVec(d2)+"\t";
+ fw.write(pri+"\t");
+ }
+ }
+ fw.write("\n");
+ fw.close();
+ }
+ FileWriter fw = new FileWriter(dir+report_nodes.get(j).getName(), true);
+ String pri = i+"\t";
+ for(int m=0;m<ratemodel.get_dists().length;m++){
+ int [][][] iters = rr.iter_dist_splits_weighted(ratemodel.get_dists()[m]);
+
+ for(int k=0;k<iters.length;k++){
+ int [] d1 = iters[k][0];
+ int [] d2 = iters[k][1];
+ String get = jade.math.Utils.printIVec(d1)+"_"+jade.math.Utils.printIVec(d2);
+ pri += ((HashMap<String ,Double>)report_nodes.get(j).getObject("internal_combined_likelihoods")).
+ get((String)get)+"\t";
+ }
+ }
+ fw.write(pri+"\n");
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void start_run_single(int n){
+ Random ran = new Random();
+ jade.math.ExponentialDistribution disp = new jade.math.ExponentialDistribution();
+ disp.setFallOff(0.5);
+ jade.math.ExponentialDistribution ext = new jade.math.ExponentialDistribution();
+ ext.setFallOff(10);
+ double cur_disp = 0.5;
+ double cur_ext = 9.8;
+ rr = new MarginalRangeReconstructor(ratemodel, tree, aln);
+ rr.set_dipsersal(cur_disp);
+ rr.set_extinction(cur_ext);
+ rr.set_ratemodel();
+ double cur_score = Math.log(rr.eval_likelihood());
+ boolean disp_bool = true;
+ double new_disp = cur_disp;
+ double new_ext = cur_ext;
+ for(int i=0;i<n;i++){
+ if(disp_bool == true){
+ new_disp = Math.abs(cur_disp -(disp_sliding_window/2)+(disp_sliding_window*ran.nextDouble()));
+ disp_bool = false;
+ }
+ else{
+ new_ext = Math.abs(cur_ext -(ext_sliding_window/2)+(ext_sliding_window*ran.nextDouble()));
+ disp_bool = true;
+ }
+ rr = new MarginalRangeReconstructor(ratemodel, tree, aln);
+ rr.set_dipsersal(new_disp);
+ rr.set_extinction(new_ext);
+ rr.set_ratemodel();
+ double new_score =Math.log(rr.eval_likelihood());
+ /*
+ * accept or reject
+ */
+ boolean acc = false;
+ double disp_prior_ratio = disp.getPDF(new_disp)/disp.getPDF(cur_disp);
+ double ext_prior_ratio = ext.getPDF(new_ext)/ext.getPDF(cur_ext);
+ double like_ratio = new_score/cur_score;
+ double dou_acc = disp_prior_ratio*ext_prior_ratio*like_ratio;
+ double min_acc = Math.min(1, dou_acc);
+ if(ran.nextDouble()<min_acc){
+ acc = true;
+ }
+ if(acc == true){
+ cur_disp = new_disp;
+ cur_ext = new_ext;
+ cur_score = new_score;
+ }
+ if((i%print_freq) == 0){
+ System.out.println(i+"\t"+cur_disp+"\t"+cur_ext+"\t"+cur_score+
+ "\t" +new_disp
+ +"\t"+new_ext
+ +"\t"+new_score
+ +"\t"+cur_score
+ +"\t"+like_ratio+"\t"+dou_acc);
+ }
+ if((i%report_freq) == 0 && to_file == true){
+ try {
+ FileWriter fw = new FileWriter(outfile, true);
+ fw.write(i+"\t"+cur_disp+"\t"+cur_ext+"\t"+cur_score+"\n");
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+}//class
diff --git a/src/jade/reconstruct/area/BayesianMarginalRangeReconstructorMult.java b/src/jade/reconstruct/area/BayesianMarginalRangeReconstructorMult.java
new file mode 100644
index 0000000..e097626
--- /dev/null
+++ b/src/jade/reconstruct/area/BayesianMarginalRangeReconstructorMult.java
@@ -0,0 +1,237 @@
+package jade.reconstruct.area;
+
+import jade.tree.*;
+import jade.data.*;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.*;
+
+public class BayesianMarginalRangeReconstructorMult{
+ String LTGN = "likelihood_of_tree_given_n";
+ String MAPLTGN = "map_of_likelihood_of_tree_given_n";
+ private RateModel ratemodel;
+ private ArrayList<RangeReconstructor> rr = new ArrayList<RangeReconstructor>();
+ private ArrayList<Tree> tree;
+ private ArrayList<Alignment> aln;
+ private double disp_sliding_window = 0.01;
+ private double ext_sliding_window = 0.01;
+ private int print_freq = 10;
+ private int report_freq = 10;
+ private int disp_para;
+ private boolean to_file = true;
+ private String outfile;
+
+ public BayesianMarginalRangeReconstructorMult(RateModel ratemodel, ArrayList<Tree> tree,ArrayList<Alignment> aln) {
+ this.ratemodel = ratemodel;
+ this.aln = aln;
+ this.tree = tree;
+ this.disp_para = ratemodel.getPeriods().size()*
+ ((ratemodel.get_areas().size()*ratemodel.get_areas().size())-ratemodel.get_areas().size());
+ System.out.println(this.disp_para);
+ }
+
+ public void start_run(int n, String outfile1){
+ this.outfile = outfile1;
+ File outf = new File(outfile);
+ outf.delete();
+ double accept = 0;
+ double reject = 0;
+ try {
+ FileWriter fw = new FileWriter(outfile, true);
+ fw.write("rep"+"\t");
+ for(int i=0;i<disp_para;i++){
+ fw.write("disp"+i+"\t");
+ }
+ fw.write("ext"+"\t"+"score"+"\n");
+ /*
+ *
+ */
+ Random ran = new Random();
+ /*
+ * priors
+ */
+ jade.math.ExponentialDistribution disp = new jade.math.ExponentialDistribution();
+ jade.math.ExponentialDistribution ext = new jade.math.ExponentialDistribution();
+ disp.setFallOff(0.02);
+ ext.setFallOff(0.02);
+ //jade.math.NormalDistribution disp = new jade.math.NormalDistribution();
+ //jade.math.NormalDistribution ext = new jade.math.NormalDistribution();
+ //disp.setMean(0.02);
+ //disp.setStDev(0.5);
+ //ext.setMean(0.02);
+ //ext.setStDev(0.5);
+ /*
+ * start the initial chain
+ */
+ double cur_ext = 0.02;
+ for(int i=0;i<tree.size();i++){
+ rr.add(new MarginalRangeReconstructor(ratemodel, tree.get(i), aln.get(i)));
+ }
+
+ ArrayList<ArrayList<ArrayList<Double>>> dold = new ArrayList<ArrayList<ArrayList<Double>>> ();
+ for(int j=0;j<ratemodel.getPeriods().size();j++){//period
+ ArrayList<ArrayList<Double>> tp = new ArrayList<ArrayList<Double>>();
+ for(int k=0;k<ratemodel.get_areas().size();k++){
+ ArrayList<Double> tpp = new ArrayList<Double>();
+ for(int l=0;l<ratemodel.get_areas().size();l++){
+ if(k!=l)
+ tpp.add(0.02);
+ else
+ tpp.add(0.0);
+ }
+ tp.add(tpp);
+ }
+ dold.add(tp);
+ }
+ ArrayList<ArrayList<ArrayList<Double>>> dnew = new ArrayList<ArrayList<ArrayList<Double>>> ();
+ for(int j=0;j<ratemodel.getPeriods().size();j++){//period
+ ArrayList<ArrayList<Double>> tp = new ArrayList<ArrayList<Double>>();
+ for(int k=0;k<ratemodel.get_areas().size();k++){
+ ArrayList<Double> tpp = new ArrayList<Double>();
+ for(int l=0;l<ratemodel.get_areas().size();l++){
+ if(k!=l)
+ tpp.add(0.02);
+ else
+ tpp.add(0.0);
+ }
+ tp.add(tpp);
+ }
+ dnew.add(tp);
+ }
+ int change = ran.nextInt(ratemodel.getPeriods().size()+1);
+ double new_ext = cur_ext;
+ double cur_score = eval_likelihood(dold,new_ext);
+ for(int i=0;i<n;i++){//numofreps
+ change = ran.nextInt(ratemodel.getPeriods().size()+1);
+ int cur = 0;
+ if(change == 0){
+ new_ext = Math.abs(cur_ext +((ran.nextDouble()-0.5)*ext_sliding_window));
+ }else{
+ change = change -1;
+ for(int j=0;j<dold.size();j++){//period
+ if(cur == change){
+ for(int k=0;k<dold.get(j).size();k++){
+ for(int l=0;l<dold.get(j).get(k).size();l++){
+ if(k != l){
+ dnew.get(j).get(k).set(l,
+ Math.abs(dold.get(j).get(k).get(l)
+ +((ran.nextDouble()-0.5)*disp_sliding_window)));
+ }
+ }
+ }
+ }else{
+ for(int k=0;k<dold.get(j).size();k++){
+ for(int l=0;l<dold.get(j).get(k).size();l++){
+ if(k != l){
+ dnew.get(j).get(k).set(l,
+ dold.get(j).get(k).get(l));
+ }
+ }
+ }
+ }
+ cur++;
+ }
+ }
+
+ double new_score = eval_likelihood(dnew,new_ext);
+ /*
+ * accept or reject
+ */
+ boolean acc = false;
+ double disp_prior_ratio = 1;
+ for(int j=0;j<dold.size();j++){//period
+ for(int k=0;k<dold.get(j).size();k++){
+ for(int l=0;l<dold.get(j).get(k).size();l++){
+ if(k!=l){
+ //System.out.println(disp.getPDF(dnew.get(j).get(k).get(l)));
+ //System.out.println(disp.getPDF(dold.get(j).get(k).get(l)));
+ disp_prior_ratio *= disp.getPDF(dnew.get(j).get(k).get(l))/
+ disp.getPDF(dold.get(j).get(k).get(l));
+ }
+ }
+ }
+ }
+
+ double ext_prior_ratio = ext.getPDF(new_ext)/ext.getPDF(cur_ext);
+ double like_ratio = Math.exp(cur_score-new_score);
+ double dou_acc = disp_prior_ratio*ext_prior_ratio*like_ratio;
+ //double dou_acc = like_ratio;
+ double min_acc = Math.min(1, dou_acc);
+ if(ran.nextDouble()<min_acc){
+ acc = true;
+ }
+ if(acc == true){
+ copy(dold,dnew);
+ cur_ext = new_ext;
+ cur_score = new_score;
+ accept ++;
+ }else{
+ reject ++;
+ }
+ if (accept > reject){
+ disp_sliding_window *= Math.exp(1.0/accept);
+ ext_sliding_window *= Math.exp(1.0/accept);
+ }else if (accept < reject){
+ disp_sliding_window /= Math.exp(1.0/reject);
+ ext_sliding_window /= Math.exp(1.0/reject);
+ }
+ if((i%print_freq) == 0){
+ System.out.println(i
+ +"\t"+new_score
+ +"\t"+cur_score
+ +"\t"+like_ratio+"\t"+dou_acc+"\t"+accept+"\t"+reject);
+ }
+ if((i%report_freq) == 0 && to_file == true){
+ try {
+ fw.write(i+"\t");
+ for(int j=0;j<dold.size();j++){//period
+ for(int k=0;k<dold.get(j).size();k++){
+ for(int l=0;l<dold.get(j).get(k).size();l++){
+ if(k!=l)
+ fw.write(dold.get(j).get(k).get(l)+"\t");
+ }
+ }
+ }
+ fw.write(cur_ext+"\t"+cur_score+"\n");
+ fw.flush();
+
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ private void copy(ArrayList<ArrayList<ArrayList<Double>>> d1,
+ ArrayList<ArrayList<ArrayList<Double>>> d2){
+ for(int j=0;j<d1.size();j++){//period
+ for(int k=0;k<d1.get(j).size();k++){
+ for(int l=0;l<d1.get(j).get(k).size();l++){
+ d1.get(j).get(k).set(l, d2.get(j).get(k).get(l));
+ }
+ }
+ }
+ }
+ private double eval_likelihood(ArrayList<ArrayList<ArrayList<Double>>> d,double e){
+ double lh = 1;
+ for(int i=0;i<rr.size();i++){
+ for(int j = 0;j<d.size();j++){
+ rr.get(i).set_dispersalmatrix(d.get(j),j);
+ }
+ rr.get(i).set_extinction(e);
+ rr.get(i).set_ratemodel();
+ lh += -Math.log(rr.get(i).eval_likelihood());
+ }
+ return lh;
+ }
+
+}//class
diff --git a/src/jade/reconstruct/area/BranchSegment.java b/src/jade/reconstruct/area/BranchSegment.java
new file mode 100644
index 0000000..b0a5c91
--- /dev/null
+++ b/src/jade/reconstruct/area/BranchSegment.java
@@ -0,0 +1,54 @@
+package jade.reconstruct.area;
+
+import java.util.*;
+
+public class BranchSegment {
+ private double start; //old
+ private double end; //young
+ private int period;
+ private int number;
+ private int [] startdist;
+ private ArrayList<int[]> startdists;
+ private double [] conditionals;
+
+ public BranchSegment(double start, double end, int period, int number, int [] startdist){
+ this.start = start;
+ this.end = end;
+ this.period = period;
+ this.number = number;
+ this.startdist = startdist;
+ startdists = new ArrayList<int[]>();
+ startdists.add(startdist);
+ }
+
+ public BranchSegment(double start, double end, int number, int period){
+ this.start = start;
+ this.end = end;
+ this.period = period;
+ this.number = number;
+ this.startdist = null;
+ startdists = new ArrayList<int[]>();
+ }
+
+ public double get_duration(){return start-end;}
+ public double get_start(){return start;}
+ public double get_end(){return end;}
+ public int get_period(){return period;}
+ public int get_number(){return number;}
+ public int [] get_startdist(){return startdist;}
+ public ArrayList<int[]> get_startdists(){return startdists;}
+ public double [] get_conditionals(){return conditionals;}
+ public void set_conditionals(double [] cond){this.conditionals = cond;}
+ public void set_start(double start){this.start = start;}
+ public void set_end(double end){this.end = end;}
+ public void set_number(int number){this.number = number;}
+ public void set_period(int period){this.period = period;}
+ public void set_startdist(int [] startdist){
+ this.startdist = startdist;
+ startdists = new ArrayList<int[]>();
+ startdists.add(startdist);
+ }
+ public void set_startdists(ArrayList<int []>sd){
+ this.startdists = sd;
+ }
+}
diff --git a/src/jade/reconstruct/area/JointRangeReconstructor.java b/src/jade/reconstruct/area/JointRangeReconstructor.java
new file mode 100644
index 0000000..99796a8
--- /dev/null
+++ b/src/jade/reconstruct/area/JointRangeReconstructor.java
@@ -0,0 +1,243 @@
+package jade.reconstruct.area;
+
+import jade.math.AP_Praxis;
+import jade.tree.*;
+import jade.data.*;
+import java.util.*;
+
+public class JointRangeReconstructor implements RangeReconstructor {
+ private RateModel ratemodel;
+
+ private String LTGN = "likelihood_of_tree_given_n";
+
+ private Tree tree;
+
+ private Alignment aln;
+
+ private String COND = "conditionals";
+
+ private String BS = "branch_segment";
+
+ public JointRangeReconstructor(RateModel ratemodel, Tree tree,
+ Alignment aln) {
+ this.ratemodel = ratemodel;
+ this.aln = aln;
+ this.tree = tree;
+ TreeUtils tu = new TreeUtils();
+ tu.setDistanceFromTip(tree);
+ setup_branch_segments(tree.getRoot());
+ setup_tip_conditionals();
+ }
+
+ public double eval_likelihood() {
+ ancdist_conditional_like(tree.getRoot());
+ return jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND));
+ }
+
+ public void set_localextinction(ArrayList<Double> e){
+ ratemodel.set_localextinction(e);
+ }
+
+ public void set_dispersalmatrix(ArrayList<ArrayList<Double>> d, int period){
+ ratemodel.set_dispersalmatrix(d, period);
+ }
+
+ public void set_dipsersal(double d) {
+ ratemodel.set_dispersal(d);
+ }
+
+ public void set_extinction(double e) {
+ ratemodel.set_extinction(e);
+ }
+
+ public void set_ratemodel() {
+ ratemodel.setup(false);
+ }
+
+ public Tree get_tree() {
+ return tree;
+ }
+
+ public int get_num_areas(){
+ return ratemodel.get_areas().size();
+ }
+ /*
+ * private
+ */
+ private void ancdist_conditional_like(Node node) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ ancdist_conditional_like(node.getChild(i));
+ }
+ double[] distconds = new double[ratemodel.get_dists().length];
+ if (node.isInternal()) {
+ double[] c1 = conditionals(node.getChild(0));
+ double[] c2 = conditionals(node.getChild(1));
+ ArrayList<AncSplit> ancsplits = new ArrayList<AncSplit>();
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ double lh = 0.0;
+ int[][][] tret = iter_dist_splits_weighted(ratemodel.get_dists()[i]);
+ double maxl = 0;
+ double maxr = 0;
+ for (int j = 0; j < tret.length; j++) {
+ int[] d1 = tret[j][0];// left
+ int[] d2 = tret[j][1];// right
+ if(c1[ratemodel.get_distribution_index(d1)]>maxl){
+ maxl = c1[ratemodel.get_distribution_index(d1)];
+ }
+ if(c2[ratemodel.get_distribution_index(d2)]>maxr){
+ maxr = c2[ratemodel.get_distribution_index(d2)];
+ }
+ //double lh_part = (c1[ratemodel.get_distribution_index(d1)] * c2[ratemodel
+ // .get_distribution_index(d2)]);
+ //lh += lh_part * weight;
+ //AncSplit as = new AncSplit(ratemodel.get_dists()[i],
+ // tret[j], weight, lh_part);
+ //ancsplits.add(as);
+ }
+ double lh_part = maxl * maxr;
+ lh += lh_part * weight;
+ distconds[i] = lh;
+ }
+ //node.assocObject(ANC, ancsplits);
+ } else {
+ distconds = ((ArrayList<BranchSegment>) node.getObject(BS)).get(0)
+ .get_conditionals();
+ }
+ if (node.hasParent()) {
+ ((ArrayList<BranchSegment>) node.getObject(BS)).get(0)
+ .set_conditionals(distconds);
+ node.assocObject(COND, distconds);
+ } else {
+ node.assocObject(COND, distconds);// root
+ }
+ }
+
+ private double[] conditionals(Node node) {
+ double[] ret = new double[ratemodel.get_dists().length];
+ ArrayList<BranchSegment> segs = (ArrayList<BranchSegment>) node
+ .getObject(BS);
+ ret = segs.get(0).get_conditionals();//conditionals at beginning of branch
+ // System.out.println("b\t"+jade.math.Utils.printDVec(ret));
+ int num_of_periods = ratemodel.getPeriods().size();
+ for (int seg = 0; seg < segs.size(); seg++) {
+ segs.get(seg).set_conditionals(ret);
+ double[][] p = ratemodel.P((num_of_periods - 1) - segs.get(seg).get_period(),
+ segs.get(seg).get_duration());
+ double[] v = new double[ratemodel.get_dists().length];
+ ArrayList<Integer> distrange = new ArrayList<Integer>();
+ if (segs.get(seg).get_startdists().size() > 0){
+ for(int i=0;i<segs.get(seg).get_startdists().size();i++){
+ distrange.add(ratemodel.get_distribution_index(segs.get(seg)
+ .get_startdists().get(i)));
+ }
+ }
+ else {
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ distrange.add(i);
+ }
+ }
+ for (int i = 0; i < distrange.size(); i++) {
+ // P[i] is the vector of probabilities of going from dist i
+ // to all other dists
+ double[] tv = new double[v.length];
+ for (int j = 0; j < tv.length; j++) {
+ tv[j] = ret[j] * p[distrange.get(i)][j];
+ }
+ v[distrange.get(i)] = jade.math.Utils.sum(tv);
+ }// for
+ // System.out.println("d\t"+segs.get(seg).get_period());
+ ret = v;
+ }// for
+ // System.out.println("a\t"+jade.math.Utils.printDVec(ret));
+ node.assocObject(LTGN, ret);
+ return ret;
+ }
+
+ private void setup_tip_conditionals() {
+ for (int i = 0; i < tree.getExternalNodeCount(); i++) {
+ double[] cond = new double[ratemodel.get_dists().length];
+ for (int j = 0; j < cond.length; j++) {
+ cond[j] = 0.0;
+ }
+ int id = 0;
+ for (int j = 0; j < aln.getIdCount(); j++) {
+ if (tree.getExternalNode(i).getName().compareTo(
+ aln.getIdentifier(j)) == 0) {
+ id = j;
+ }// if
+ }// for
+ cond[ratemodel.get_distribution_index(aln
+ .getAlignedSequenceString(id))] = 1.0;
+ ArrayList<BranchSegment> bs = (ArrayList<BranchSegment>) tree
+ .getExternalNode(i).getObject(BS);
+ bs.get(0).set_conditionals(cond);
+ }
+ }
+
+ private void setup_branch_segments(Node node) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ setup_branch_segments(node.getChild(i));
+ }
+ if (node.hasParent()) {
+ ArrayList<BranchSegment> abs = new ArrayList<BranchSegment>();
+ double anc = node.getParent().getDistanceFromTip();// getage
+ double dec = node.getDistanceFromTip();// getage
+ double t = dec;
+ int n = 0;
+ while (t < anc) {
+ int p = ratemodel.getRelevantPeriod(t);
+ double p_start = ratemodel.getPeriods().get(p);
+ double s = 0.0;// start
+ if (anc < p_start) {// segment is shorter than period
+ s = anc;
+ // t = anc;
+ } else {// segment is longer than period
+ s = p_start;
+ // t = p_start;
+ }
+ BranchSegment bs = new BranchSegment(s, t, n, p);// start,
+ // end,
+ // number,
+ // period
+ abs.add(bs);
+ // System.out.println(s+"\t"+t);
+ t = s;
+ n++;
+ }// while
+ node.assocObject(BS, abs);// these are stored from youngest to
+ // oldest, so 0 will be the one closest
+ // to the tip and >0 is closer to node
+ }// if
+ }// private
+
+ private int[][][] iter_dist_splits_weighted(int[] dist) {
+ int[][][] ret = new int[0][0][0];
+ if (jade.math.Utils.sum(dist) == 1) {
+ ret = jade.math.Utils.iter_splitranges(dist);
+ weight = 1.0;
+ } else {
+ weight = 1.0 / (jade.math.Utils.sum(dist) * 4);
+ ret = jade.math.Utils.iter_splitranges(dist);
+
+ //for sp in iter_dist_splits(dist):
+ // yield sp, wt
+ }
+ return ret;
+ }
+
+ private double weight;
+
+ public static void main(String [] args){
+ int [][][] ret = jade.math.Utils.iter_splitranges(new int[] {1,0,0});
+ for(int i=0;i<ret.length;i++){
+ for(int j=0;j<ret[i][0].length;j++){
+ System.out.print(ret[i][0][j]);
+ }
+ System.out.print("\t");
+ for(int j=0;j<ret[i][1].length;j++){
+ System.out.print(ret[i][1][j]);
+ }
+ System.out.println();
+ }
+ }
+}//class
diff --git a/src/jade/reconstruct/area/MarginalRangeReconstructor.java b/src/jade/reconstruct/area/MarginalRangeReconstructor.java
new file mode 100644
index 0000000..49ca5dc
--- /dev/null
+++ b/src/jade/reconstruct/area/MarginalRangeReconstructor.java
@@ -0,0 +1,526 @@
+package jade.reconstruct.area;
+
+import jade.math.AP_Praxis;
+import jade.tree.*;
+import jade.data.*;
+import java.util.*;
+
+public class MarginalRangeReconstructor implements RangeReconstructor{
+ private RateModel ratemodel;
+
+ private String LTGN = "likelihood_of_tree_given_n";
+ private String DISTMAP = "internal_combined_likelihoods";
+ private String MAPLTGN = "map_of_likelihood_of_tree_given_n";
+
+ private String ANCMAP = "ancmap";
+
+ private Tree tree;
+
+ private Alignment aln;
+
+ private String COND = "conditionals";
+
+ private String BS = "branch_segment";
+
+ private String ANC = "ancsplits";
+
+ public MarginalRangeReconstructor(RateModel ratemodel, Tree tree,
+ Alignment aln) {
+ this.ratemodel = ratemodel;
+ this.aln = aln;
+ this.tree = tree;
+ this.setDistanceFromTipForBG();
+ setup_branch_segments(tree.getRoot());
+ setup_tip_conditionals();
+ }
+
+ public int get_num_areas(){
+ return ratemodel.get_areas().size();
+ }
+
+ public double eval_likelihood() {
+ ancdist_conditional_like(tree.getRoot());
+ return jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND));
+ }
+
+ public void set_internals_likelihoods(){
+ preorder_set_internal_conditionals(tree.getRoot());
+ }
+
+ public void set_dipsersal(double d) {
+ ratemodel.set_dispersal(d);
+ }
+
+ public void set_extinction(double e) {
+ ratemodel.set_extinction(e);
+ }
+
+ /*
+ * need code to make sure that local extinction array is the correct size
+ */
+ public void set_localextinction(ArrayList<Double> e){
+ ratemodel.set_localextinction(e);
+ }
+
+ public void set_dispersalmatrix(ArrayList<ArrayList<Double>> d, int period){
+ ratemodel.set_dispersalmatrix(d, period);
+ }
+
+ public void set_ratemodel() {
+ ratemodel.setup(false);
+ }
+
+ public Tree get_tree() {
+ return tree;
+ }
+
+ /*
+ * private
+ */
+ private void ancdist_conditional_like(Node node) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ ancdist_conditional_like(node.getChild(i));
+ }
+ double[] distconds = new double[ratemodel.get_dists().length];
+ if (node.isInternal()) {
+ double[] c1 = conditionals(node.getChild(0));
+ double[] c2 = conditionals(node.getChild(1));
+ ArrayList<AncSplit> ancsplits = new ArrayList<AncSplit>();
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ double lh = 0.0;
+ int[][][] tret = iter_dist_splits_weighted(ratemodel
+ .get_dists()[i]);
+ for (int j = 0; j < tret.length; j++) {
+ int[] d1 = tret[j][0];// left
+ int[] d2 = tret[j][1];// right
+ double lh_part = (c1[ratemodel.get_distribution_index(d1)] * c2[ratemodel
+ .get_distribution_index(d2)]);
+ lh += lh_part * weight;
+ AncSplit as = new AncSplit(ratemodel.get_dists()[i],
+ tret[j], weight, lh_part);
+ ancsplits.add(as);
+ }
+ distconds[i] = lh;
+ }
+ //node.assocObject(ANC, ancsplits);
+ } else {
+ distconds = ((ArrayList<BranchSegment>) node.getObject(BS)).get(0)
+ .get_conditionals();
+ }
+ if (node.hasParent()) {
+ ((ArrayList<BranchSegment>) node.getObject(BS)).get(0)
+ .set_conditionals(distconds);
+ node.assocObject(COND, distconds);
+ } else {
+ node.assocObject(COND, distconds);// root
+ }
+ }
+
+ private double[] conditionals(Node node) {
+ double[] ret = new double[ratemodel.get_dists().length];
+ ArrayList<BranchSegment> segs = (ArrayList<BranchSegment>) node.getObject(BS);
+ ret = segs.get(0).get_conditionals();//conditionals at beginning of branch
+ // System.out.println("b\t"+jade.math.Utils.printDVec(ret));
+ int num_of_periods = ratemodel.getPeriods().size();
+ for (int seg = 0; seg < segs.size(); seg++) {
+ segs.get(seg).set_conditionals(ret);
+ double[][] p = ratemodel.P((num_of_periods - 1) - segs.get(seg).get_period(),
+ segs.get(seg).get_duration());
+ double[] v = new double[ratemodel.get_dists().length];
+ ArrayList<Integer> distrange = new ArrayList<Integer>();
+ if (segs.get(seg).get_startdists().size() > 0){
+ for(int i=0;i<segs.get(seg).get_startdists().size();i++){
+ distrange.add(ratemodel.get_distribution_index(segs.get(seg).get_startdists().get(i)));
+ }
+ }
+ else {
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ distrange.add(i);
+ }
+ }
+ for (int i = 0; i < distrange.size(); i++) {
+ // P[i] is the vector of probabilities of going from dist i
+ // to all other dists
+ double[] tv = new double[v.length];
+ for (int j = 0; j < tv.length; j++) {
+ tv[j] = ret[j] * p[distrange.get(i)][j];
+ }
+ v[distrange.get(i)] = jade.math.Utils.sum(tv);
+ }// for
+ //System.out.println("d\t"+segs.get(seg).get_period());
+ ret = v;
+ }// for
+ //System.out.println("a\t"+jade.math.Utils.printDVec(ret));
+ return ret;
+ }
+
+ /*
+ * doesn't seem to work yet
+ */
+
+ private void preorder_set_internal_conditionals(Node node) {
+ if ((tree.getRoot() != node) && !node.isExternal()) {
+ // conditional likelihood excluding X
+ double[] clex = calcExclude(node.getParent(), node);
+ // L(T|X)
+ double[] x = new double[ratemodel.get_dists().length];
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ x[i] = ((double [])node.getObject(COND))[i];
+ double t = 0;
+ for (int j = 0; j < ratemodel.get_dists().length; j++) {
+ t = t + calc_cond_p_for_excl(clex[j], node, i,j);
+ }
+ x[i] = x[i] * t;
+ }
+ node.assocObject(LTGN, x);
+ }
+ for (int i = 0; i < node.getChildCount(); i++) {
+ preorder_set_internal_conditionals(node.getChild(i));
+ }
+ }
+
+ /*
+ * goes with preorder...
+ * doesn't work yet
+ */
+
+ private double[] calcExclude(Node node, Node excl) {
+ double[] clex = new double[ratemodel.get_dists().length];
+ for (int i = 0; i < ratemodel.get_dists().length; i++) {
+ clex[i] = 1.0;
+ }
+ // conditional likelihood excluding X
+ for (int i = 0; i < node.getChildCount(); i++) {
+ if (node.getChild(i) != excl) {
+ for (int j = 0; j < ratemodel.get_dists().length; j++) {
+ double t = 0;
+ for (int k = 0; k < ratemodel.get_dists().length; k++) {
+ t = t + calc_cond_p_for_excl(((double[])node.getChild(i).getObject(COND))[k],node.getChild(i),j,k);
+ }
+ clex[j] = clex[j] * t;
+ }
+ }
+ }
+ Node mother = node.getParent();
+ if (mother != null) {
+ for (int i = 0; i < mother.getChildCount(); i++) {
+ if (mother.getChild(i) == node) {
+ double[] clExclude = calcExclude(mother, node);
+ for (int j = 0; j < ratemodel.get_dists().length; j++) {
+ double t = 0;
+ for (int k = 0; k < ratemodel.get_dists().length; k++) {
+ t = t + calc_cond_p_for_excl(clExclude[k], mother.getChild(i),j,k);
+ }
+ clex[j] = clex[j] * t;
+ }
+ }
+ }
+ }
+ double[] d = clex;
+ return d;
+ }
+
+ /*
+ * goes with calcExclude
+ * doesn't work yet
+ */
+ private double calc_cond_p_for_excl(double value, Node node, int j, int k) {
+ double ret = value;
+ ArrayList<BranchSegment> segs = (ArrayList<BranchSegment>) node.getObject(BS);
+ int num_of_periods = ratemodel.getPeriods().size();
+ for (int seg = 0; seg < segs.size(); seg++) {
+ double[][] p = ratemodel.P((num_of_periods - 1) - segs.get(seg).get_period(),
+ segs.get(seg).get_duration());
+ ret = ret * p[j][k];
+ }// for
+ return ret;
+ }
+
+ /*
+ * slower but gets the job done
+ * does the internal nodes as the ancestral range and considers all possible scenarios for that range
+ * instaed of doing each subdivision seperateely as in all_ranges
+ */
+ public void calc_internal_likelihoods(boolean opt){
+ calc_internal_likelihoods(tree.getRoot(), opt);
+ }
+
+ private void calc_internal_likelihoods(Node node, boolean opt){
+ if(node.isInternal()){
+ for(int i=0;i<node.getChildCount();i++){
+ calc_internal_likelihoods(node.getChild(i), opt);
+ }
+ HashMap<int[],Double> anc = new HashMap<int[],Double> ();
+ node.assocObject(DISTMAP,anc);
+ //System.out.println(node.getChild(0).getName()+"\t"+node.getChild(1).getName());
+ this.clear_start_dists();
+ for(int i=0;i<ratemodel.get_dists().length;i++){
+ //for(int j=0;j<ratemodel.get_dists()[i].length;j++){System.out.print(ratemodel.get_dists()[i][j]);}
+
+ int [][][] iters = this.iter_dist_splits_weighted(ratemodel.get_dists()[i]);
+ ArrayList<int []> bd1 = new ArrayList<int []> ();
+ ArrayList<int []> bd2 = new ArrayList<int []> ();
+ for(int j=0;j<iters.length;j++){
+ bd1.add(iters[j][0]);
+ bd2.add(iters[j][1]);
+ }
+ ((ArrayList<BranchSegment>)node.getChild(0).getObject(BS)).get(0).set_startdists(bd1);
+ ((ArrayList<BranchSegment>)node.getChild(1).getObject(BS)).get(0).set_startdists(bd2);
+ /*
+ * opt code
+ */
+ if(opt == true){
+ double [] init = {666,0.1,0.1};
+ double [] in = new double [10];
+ in [0] = jade.math.PrecisionCalculator.getMachinePrecision();
+ //in [0] = 0.00000000001;
+ in [1] = 1.1102230246251565E-8;
+ //in [1] = 3.1622776601683794E-6;
+ //System.out.println("s "+Math.sqrt(in[0]));
+ in [2] = 0.000001;
+ in [5] = 1000;
+ in [6] = 0.01;
+ in [7] = 1;
+ in [8] = 1;
+ in [9] = -1;
+ double [] out = new double [7];
+ RangeReconstructionOptimizer rec = new RangeReconstructionOptimizer(this);
+ try{
+ AP_Praxis.praxis(2, init, rec, in, out);
+ }catch(java.lang.ArithmeticException ae){
+ continue;
+ }
+ System.out.print("\t"+
+ Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND)))
+ +"\t"+init[1]+"\t"+init[2]+"\t"+out[2]+"\n"
+ );
+ }/*
+ * end opt code
+ */
+ else{
+ this.eval_likelihood();
+ ((HashMap<int [] ,Double>)node.getObject(DISTMAP)).put(
+ ratemodel.get_dists()[i],
+ -Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND))));
+ //System.out.print("\t"+
+ // Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND)))
+ // +"\n"
+ //);
+ }
+ }
+ }
+ }
+
+ private void clear_start_dists(){
+ for(int j=0;j<tree.getExternalNodeCount();j++){
+ Node node = tree.getExternalNode(j);
+ if(((ArrayList<BranchSegment>)node.getObject(BS))!=null){
+ for(int i=0;i<((ArrayList<BranchSegment>)node.getObject(BS)).size();i++){
+ ((ArrayList<BranchSegment>)node.getObject(BS)).get(i).set_startdists(new ArrayList<int[]>());
+ }
+ }
+ }
+ for(int j=0;j<tree.getInternalNodeCount();j++){
+ Node node = tree.getInternalNode(j);
+ if(((ArrayList<BranchSegment>)node.getObject(BS))!=null){
+ for(int i=0;i<((ArrayList<BranchSegment>)node.getObject(BS)).size();i++){
+ ((ArrayList<BranchSegment>)node.getObject(BS)).get(i).set_startdists(new ArrayList<int[]>());
+ }
+ }
+ }
+ }
+
+ public void calc_internal_likelihoods_all_ranges(boolean opt){
+ //calc_internal_likelihoods_all_ranges(tree.getInternalNode("M"),opt);
+ calc_internal_likelihoods_all_ranges(tree.getRoot(),opt);
+ }
+ private void calc_internal_likelihoods_all_ranges(Node node, boolean opt){
+ for(int i=0;i<node.getChildCount();i++){
+ calc_internal_likelihoods_all_ranges(node.getChild(i),opt);
+ }
+ if(node.isInternal()){
+ //System.out.println(node.getChild(0).getName()+"\t"+node.getChild(1).getName());
+ ArrayList<Double> lt = new ArrayList<Double>();
+ node.assocObject(LTGN,lt);
+ HashMap<Double,String> mlt = new HashMap<Double,String>();
+ node.assocObject(MAPLTGN,mlt);
+ HashMap<Double,String> anc = new HashMap<Double,String>();
+ node.assocObject(ANCMAP,anc);
+ HashMap<String,Double> a = new HashMap<String,Double> ();
+ node.assocObject(DISTMAP,a);
+ this.clear_start_dists();
+ //this.setup_branch_segments(tree.getRoot());
+ //this.setup_tip_conditionals();
+ for(int i=0;i<ratemodel.get_dists().length;i++){
+ int [][][] iters = this.iter_dist_splits_weighted(ratemodel.get_dists()[i]);
+ for(int j=0;j<iters.length;j++){
+ int [] d1 = iters[j][0];
+ int [] d2 = iters[j][1];
+ int which0 = ((ArrayList<BranchSegment>)node.getChild(0).getObject(BS)).size()-1;
+ ((ArrayList<BranchSegment>)node.getChild(0).getObject(BS)).get(which0).set_startdist(d1);
+ int which1 = ((ArrayList<BranchSegment>)node.getChild(1).getObject(BS)).size()-1;
+ ((ArrayList<BranchSegment>)node.getChild(1).getObject(BS)).get(which1).set_startdist(d2);
+ //could add optimization code here
+ /*
+ * opt code
+ */
+ if(opt == true){
+ double [] init = {666,0.1,0.1};
+ double [] in = new double [10];
+ in [0] = jade.math.PrecisionCalculator.getMachinePrecision();
+ //in [0] = 0.00000000001;
+ in [1] = 1.1102230246251565E-8;
+ //in [1] = 3.1622776601683794E-6;
+ //System.out.println("s "+Math.sqrt(in[0]));
+ in [2] = 0.000001;
+ in [5] = 1000;
+ in [6] = 0.01;
+ in [7] = 1;
+ in [8] = 1;
+ in [9] = -1;
+ double [] out = new double [7];
+ RangeReconstructionOptimizer rec = new RangeReconstructionOptimizer(this);
+ try{
+ AP_Praxis.praxis(2, init, rec, in, out);
+ }catch(java.lang.ArithmeticException ae){
+ continue;
+ }
+
+ /*
+ * end opt code
+ */
+ //System.out.println(
+ // jade.math.Utils.printCombIVec(d1,d2)+" "+
+ // jade.math.Utils.printIVec(d1)+" "+
+ // jade.math.Utils.printIVec(d2)+" "+
+ // "\t"+init[1]+"\t"+init[2]+"\t"+
+ // out[2]
+ //);
+ }else{
+ this.eval_likelihood();
+ /*System.out.print(jade.math.Utils.printCombIVec(d1,d2)+" "+
+ jade.math.Utils.printIVec(d1)+" "+
+ jade.math.Utils.printIVec(d2)+" "+
+ Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND)))+"\n"
+ );*/
+ ((ArrayList<Double>)node.getObject(LTGN)).
+ add(-Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND))));
+ ((HashMap<Double,String>)node.getObject(ANCMAP)).put(
+ -Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND))),
+ jade.math.Utils.printCombIVec(d1,d2));
+ ((HashMap<Double,String>)node.getObject(MAPLTGN)).put(
+ -Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND))),
+ jade.math.Utils.printCombIVec(d1,d2)+"\t"+
+ jade.math.Utils.printIVec(d1)+"\t"+
+ jade.math.Utils.printIVec(d2));
+ ((HashMap<String,Double>)node.getObject(DISTMAP)).put(
+ (jade.math.Utils.printIVec(d1)+"_"+
+ jade.math.Utils.printIVec(d2)),
+ Math.log(jade.math.Utils.sum((double[]) tree.getRoot().getObject(COND))));
+ }//else
+ }//for each iter
+ }//for each start dist
+ Collections.sort(((ArrayList<Double>)node.getObject(LTGN)));
+ }//if node is internal
+ }
+
+ private void setup_tip_conditionals() {
+ for (int i = 0; i < tree.getExternalNodeCount(); i++) {
+ double[] cond = new double[ratemodel.get_dists().length];
+ for (int j = 0; j < cond.length; j++) {
+ cond[j] = 0.0;
+ }
+ int id = 0;
+ for (int j = 0; j < aln.getIdCount(); j++) {
+ if (tree.getExternalNode(i).getName().compareTo(
+ aln.getIdentifier(j)) == 0) {
+ id = j;
+ }// if
+ }// for
+ cond[ratemodel.get_distribution_index(aln
+ .getAlignedSequenceString(id))] = 1.0;
+ ArrayList<BranchSegment> bs = (ArrayList<BranchSegment>) tree
+ .getExternalNode(i).getObject(BS);
+ bs.get(0).set_conditionals(cond);
+ }
+ }
+
+ private void setup_branch_segments(Node node) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ setup_branch_segments(node.getChild(i));
+ }
+ if (node.hasParent()) {
+ ArrayList<BranchSegment> abs = new ArrayList<BranchSegment>();
+ double anc = node.getParent().getDistanceFromTip();// getage
+ double dec = node.getDistanceFromTip();// getage
+ double t = dec;
+ int n = 0;
+ while (t < anc) {
+ int p = ratemodel.getRelevantPeriod(t);
+ double p_start = ratemodel.getPeriods().get(p);
+ double s = 0.0;// start
+ if (anc < p_start) {// segment is shorter than period
+ s = anc;
+ // t = anc;
+ } else {// segment is longer than period
+ s = p_start;
+ // t = p_start;
+ }
+ BranchSegment bs = new BranchSegment(s, t, n, p);// start,
+ // end,
+ // number,
+ // period
+ abs.add(bs);
+ // System.out.println(s+"\t"+t);
+ t = s;
+ n++;
+ }// while
+ node.assocObject(BS, abs);// these are stored from youngest to
+ // oldest, so 0 will be the one closest
+ // to the tip and >0 is closer to node
+ }// if
+ }// private
+
+ public int[][][] iter_dist_splits_weighted(int[] dist) {
+ int[][][] ret = new int[0][0][0];
+ if (jade.math.Utils.sum(dist) == 1) {
+ ret = jade.math.Utils.iter_splitranges(dist);
+ weight = 1.0;
+ } else {
+ weight = 1.0 / (jade.math.Utils.sum(dist) * 4);
+ ret = jade.math.Utils.iter_splitranges(dist);
+
+ //for sp in iter_dist_splits(dist):
+ // yield sp, wt
+ }
+ return ret;
+ }
+
+ private double weight;
+
+ public static void main(String [] args){
+ int [][][] ret = jade.math.Utils.iter_splitranges(new int[] {1,1,1,1,1,1,1});
+ for(int i=0;i<ret.length;i++){
+ for(int j=0;j<ret[i][0].length;j++){
+ System.out.print(ret[i][0][j]);
+ }
+ System.out.print("\t");
+ for(int j=0;j<ret[i][1].length;j++){
+ System.out.print(ret[i][1][j]);
+ }
+ System.out.println();
+ }System.out.println(ret.length);
+ }
+
+ private void setDistanceFromTipForBG() {
+ for (int i = 0; i < tree.getExternalNodeCount(); i++) {
+ Node cur = tree.getExternalNode(i);
+ double curh = 0.0;
+ while (cur != null) {
+ cur.setDistanceFromTip(curh);
+ curh += cur.getBL();
+ cur = cur.getParent();
+ }
+ }
+ }
+}//class
diff --git a/src/jade/reconstruct/area/MatrixExponential.java b/src/jade/reconstruct/area/MatrixExponential.java
new file mode 100644
index 0000000..beaddcf
--- /dev/null
+++ b/src/jade/reconstruct/area/MatrixExponential.java
@@ -0,0 +1,801 @@
+/*
+ * MatrixExponential.java
+ *
+ * Created on April 18, 2005, 11:02 PM
+ */
+
+package jade.reconstruct.area;
+
+/**
+ *
+ * @author stephensmith
+ */
+public class MatrixExponential implements Cloneable {
+ private static double EPSILON = 2.220446049250313E-16;
+ private double MINARC = 1.0e-9;
+ //
+ // Public stuff
+ //
+
+ // Constraints and conventions:
+ // - first argument: row
+ // - second argument: column
+ // - transition: from row to column
+ // - sum of every row = 1
+ // - sum of frequencies = 1
+ // - frequencies * rate matrix = 1 (stationarity)
+ //
+ // Private stuff
+ //
+
+ // Eigenvalues, eigenvectors, and inverse eigenvectors
+ private final double[] Eval;
+ private final double[][] Evec;
+ private final double[][] Ievc;
+ private final double[][] iexp;
+ private final int[] ordr;
+ private final double[] evali;
+ private final double amat[][];
+
+
+ /** dimension of rate matrix */
+ private final int dimension_;
+
+ /** transition probability matrix */
+ private final double[][] transProb;
+
+
+
+ /**
+ * create module
+ *
+ * @param r rate matrix
+ */
+ public MatrixExponential(int dimension) {
+ this.dimension_ = dimension;
+ if(dimension<=0) {
+ throw new IllegalArgumentException("Invalid dimension:"+dimension);
+ }
+ transProb = new double[dimension][dimension];
+ Eval = new double[dimension];
+ Evec = new double[dimension][dimension];
+ Ievc = new double[dimension][dimension];
+ iexp = new double[dimension][dimension];
+ amat = new double[dimension][dimension];
+
+ ordr = new int[dimension];
+ evali = new double[dimension];
+
+ }
+ /**
+ * create module
+ *
+ * @param r rate matrix
+ */
+// public MatrixExponential(RateMatrix r) {
+// int dimension = r.getDimension();
+// this.dimension_ = dimension;
+// transProb = new double[dimension][dimension];
+// Eval = new double[dimension];
+// Evec = new double[dimension][dimension];
+// Ievc = new double[dimension][dimension];
+// iexp = new double[dimension][dimension];
+// amat = new double[dimension][dimension];
+//
+// ordr = new int[dimension];
+// evali = new double[dimension];
+// setMatrix(r);
+// }
+
+ public final double getTransitionProbability(int from, int to) {
+ return transProb[from][to];
+ }
+
+ public int getDimension() { return dimension_; }
+ public void updateByRelativeRates(double[][] relativeRates) {
+ /* compute eigenvalues and eigenvectors */
+ for (int i = 0; i < dimension_; i++) {
+ for (int j = 0; j < dimension_; j++) {
+ amat[i][j] = relativeRates[i][j];
+ }
+ }
+
+ elmhes(amat, ordr, dimension_);
+ eltran(amat, Evec, ordr, dimension_);
+ hqr2(dimension_, 1, dimension_, amat, Evec, Eval, evali);
+ luinverse(Evec, Ievc, dimension_);
+ }
+ /**
+ * update rate matrix used in present module
+ *
+ * @param r rate matrix
+ */
+// public void setMatrix(RateMatrix r) {
+// updateByRelativeRates(r.getRelativeRates());
+// }
+
+ /**
+ * A utility method for speed, transfers trans prob information quickly
+ * into store
+ */
+ public final void getTransitionProbabilities(double[][] probabilityStore) {
+ // this check should be changed to an assertion once we adopt Java 1.4
+ if (probabilityStore == null) throw new RuntimeException("Assertion Error: probabilityStore == null");
+ copy(transProb,probabilityStore);
+ }
+// private final void setIdentity() {
+// for(int i = 0 ; i < transProb.length ; i++) {
+// final double[] row = transProb[i];
+// for(int j = 0 ; j < row.length ; j++) {
+// row[j] = (i==j) ? 1 : 0;
+// }
+// }
+// }
+ /**
+ * compute transition probabilities for a expected distance
+ * using the prespecified rate matrix
+ *
+ * @param arc expected distance
+ */
+ public final void setDistance(double arc) {
+ if(arc<MINARC) {
+ arc = MINARC;
+ }
+ int i, j, k;
+ double temp;
+ for (k = 0; k < dimension_; k++) {
+ temp = Math.exp(arc * Eval[k]);
+ for (j = 0; j < dimension_; j++) {
+ iexp[k][j] = Ievc[k][j] * temp;
+ }
+ }
+ for (i = 0; i < dimension_; i++) {
+ for (j = 0; j < dimension_; j++) {
+ temp = 0.0;
+ for (k = 0; k < dimension_; k++) {
+ temp += Evec[i][k] * iexp[k][j];
+ }
+ transProb[i][j] = Math.abs(temp);
+ }
+ }
+ }
+
+ /**
+ * compute transition probabilities for a expected distance
+ * using the prespecified rate matrix
+ *
+ * @note the resulting matrix works [to][from] as opposed to [from][to]
+ *
+ * @param arc expected distance
+ */
+ public final void setDistanceTranspose(double arc) {
+ if(arc<MINARC) {
+ arc = MINARC;
+ }
+ int i, j, k;
+ double temp;
+ for (k = 0; k < dimension_ ; k++) {
+ temp = Math.exp(arc * Eval[k]);
+ for (j = 0; j < dimension_ ; j++) {
+ iexp[k][j] = Ievc[k][j] * temp;
+ }
+ }
+ for (i = 0; i < dimension_ ; i++) {
+ for (j = 0; j < dimension_ ; j++) {
+ temp = 0.0;
+ for (k = 0; k < dimension_ ; k++) {
+ temp += Evec[i][k] * iexp[k][j];
+ }
+ //This is the line that is different from the normal setDistance method
+ //Was transProb[j][i]
+ transProb[j][i] = Math.abs(temp);
+ }
+ }
+ }
+
+
+ private void elmhes(double[][] a, int[] ordr, int n) {
+ int m, j, i;
+ double y, x;
+
+ for (i = 0; i < n; i++) {
+ ordr[i] = 0;
+ }
+ for (m = 2; m < n; m++) {
+ x = 0.0;
+ i = m;
+ for (j = m; j <= n; j++) {
+ if (Math.abs(a[j - 1][m - 2]) > Math.abs(x)) {
+ x = a[j - 1][m - 2];
+ i = j;
+ }
+ }
+ ordr[m - 1] = i;
+ if (i != m) {
+ for (j = m - 2; j < n; j++) {
+ y = a[i - 1][j];
+ a[i - 1][j] = a[m - 1][j];
+ a[m - 1][j] = y;
+ }
+ for (j = 0; j < n; j++) {
+ y = a[j][i - 1];
+ a[j][i - 1] = a[j][m - 1];
+ a[j][m - 1] = y;
+ }
+ }
+ if (x != 0.0) {
+ for (i = m; i < n; i++) {
+ y = a[i][m - 2];
+ if (y != 0.0){
+ y /= x;
+ a[i][m - 2] = y;
+ for (j = m - 1; j < n; j++) {
+ a[i][j] -= y * a[m - 1][j];
+ }
+ for (j = 0; j < n; j++) {
+ a[j][m - 1] += y * a[j][i];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Helper variables for mcdiv
+ private double cr, ci;
+
+ private void mcdiv(double ar, double ai, double br, double bi) {
+ double s, ars, ais, brs, bis;
+
+ s = Math.abs(br) + Math.abs(bi);
+ ars = ar/s;
+ ais = ai/s;
+ brs = br/s;
+ bis = bi/s;
+ s = brs * brs + bis * bis;
+ cr = (ars * brs + ais * bis)/s;
+ ci = (ais * brs - ars * bis)/s;
+ }
+
+ void hqr2(int n, int low, int hgh, double[][] h, double[][] zz,
+ double[] wr, double[] wi) throws ArithmeticException {
+ int i, j, k, l=0, m, en, na, itn, its;
+ double p=0, q=0, r=0, s=0, t, w, x=0, y, ra, sa, vi, vr, z=0, norm, tst1, tst2;
+ boolean notLast;
+
+
+ norm = 0.0;
+ k = 1;
+ /* store isolated roots and compute matrix norm */
+ for (i = 0; i < n; i++) {
+ for (j = k - 1; j < n; j++) {
+ norm += Math.abs(h[i][j]);
+ }
+ k = i + 1;
+ if (i + 1 < low || i + 1 > hgh) {
+ wr[i] = h[i][i];
+ wi[i] = 0.0;
+ }
+ }
+ en = hgh;
+ t = 0.0;
+ itn = n * 30;
+ while (en >= low) { /* search for next eigenvalues */
+ its = 0;
+ na = en - 1;
+ while (en >= 1) {
+ /* look for single small sub-diagonal element */
+ boolean fullLoop = true;
+ for (l = en; l > low; l--) {
+ s = Math.abs(h[l - 2][l - 2]) + Math.abs(h[l - 1][l - 1]);
+ if (s == 0.0) {
+ s = norm;
+ }
+ tst1 = s;
+ tst2 = tst1 + Math.abs(h[l - 1][l - 2]);
+ if (tst2 == tst1) {
+ fullLoop = false;
+ break;
+ }
+ }
+ if (fullLoop) {
+ l = low;
+ }
+
+ x = h[en - 1][en - 1]; /* form shift */
+ if (l == en || l == na) {
+ break;
+ }
+ if (itn == 0) {
+ /* eigenvalues have not converged */
+ //System.err.println("Eigenvalues not converged");
+ throw new ArithmeticException();
+ }
+ y = h[na - 1][na - 1];
+ w = h[en - 1][na - 1] * h[na - 1][en - 1];
+ /* form exceptional shift */
+ if (its == 10 || its == 20) {
+ t += x;
+ for (i = low - 1; i < en; i++) {
+ h[i][i] -= x;
+ }
+ s = Math.abs(h[en - 1][na - 1]) + Math.abs(h[na - 1][en - 3]);
+ x = 0.75 * s;
+ y = x;
+ w = -0.4375 * s * s;
+ }
+ its++;
+ itn--;
+ /* look for two consecutive small sub-diagonal elements */
+ for (m = en - 2; m >= l; m--) {
+ z = h[m - 1][m - 1];
+ r = x - z;
+ s = y - z;
+ p = (r * s - w) / h[m][m - 1] + h[m - 1][m];
+ q = h[m][m] - z - r - s;
+ r = h[m + 1][m];
+ s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+ p /= s;
+ q /= s;
+ r /= s;
+ if (m == l) {
+ break;
+ }
+ tst1 = Math.abs(p) * (Math.abs(h[m - 2][m - 2]) + Math.abs(z) + Math.abs(h[m][m]));
+ tst2 = tst1 + Math.abs(h[m - 1][m - 2]) * (Math.abs(q) + Math.abs(r));
+ if (tst2 == tst1) {
+ break;
+ }
+ }
+ for (i = m + 2; i <= en; i++) {
+ h[i - 1][i - 3] = 0.0;
+ if (i != m + 2) {
+ h[i - 1][i - 4] = 0.0;
+ }
+ }
+ for (k = m; k <= na; k++) {
+ if (k == na) {
+ notLast = false;
+ } else {
+ notLast = true;
+ }
+ if (k != m) {
+ p = h[k - 1][k - 2];
+ q = h[k][k - 2];
+ r = 0.0;
+ if (notLast) {
+ r = h[k + 1][k - 2];
+ }
+ x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+ if (x != 0.0) {
+ p /= x;
+ q /= x;
+ r /= x;
+ }
+ }
+ if (x != 0.0) {
+ if (p < 0.0) { /* sign */
+ s = - Math.sqrt(p * p + q * q + r * r);
+ } else {
+ s = Math.sqrt(p * p + q * q + r * r);
+ }
+ if (k != m) {
+ h[k - 1][k - 2] = -s * x;
+ } else if (l != m) {
+ h[k - 1][k - 2] = -h[k - 1][k - 2];
+ }
+ p += s;
+ x = p / s;
+ y = q / s;
+ z = r / s;
+ q /= p;
+ r /= p;
+ if (!notLast) {
+ for (j = k - 1; j < n; j++) { /* row modification */
+ p = h[k - 1][j] + q * h[k][j];
+ h[k - 1][j] -= p * x;
+ h[k][j] -= p * y;
+ }
+ j = (en < (k + 3)) ? en : (k + 3); /* min */
+ for (i = 0; i < j; i++) { /* column modification */
+ p = x * h[i][k - 1] + y * h[i][k];
+ h[i][k - 1] -= p;
+ h[i][k] -= p * q;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++) {
+ p = x * zz[i][k - 1] + y * zz[i][k];
+ zz[i][k - 1] -= p;
+ zz[i][k] -= p * q;
+ }
+ } else {
+ for (j = k - 1; j < n; j++) { /* row modification */
+ p = h[k - 1][j] + q * h[k][j] + r * h[k + 1][j];
+ h[k - 1][j] -= p * x;
+ h[k][j] -= p * y;
+ h[k + 1][j] -= p * z;
+ }
+ j = (en < (k + 3)) ? en : (k + 3); /* min */
+ for (i = 0; i < j; i++) { /* column modification */
+ p = x * h[i][k - 1] + y * h[i][k] + z * h[i][k + 1];
+ h[i][k - 1] -= p;
+ h[i][k] -= p * q;
+ h[i][k + 1] -= p * r;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++) {
+ p = x * zz[i][k - 1] + y * zz[i][k] +
+ z * zz[i][k + 1];
+ zz[i][k - 1] -= p;
+ zz[i][k] -= p * q;
+ zz[i][k + 1] -= p * r;
+ }
+ }
+ }
+ } /* for k */
+ } /* while infinite loop */
+ if (l == en) { /* one root found */
+ h[en - 1][en - 1] = x + t;
+ wr[en - 1] = h[en - 1][en - 1];
+ wi[en - 1] = 0.0;
+ en = na;
+ continue;
+ }
+ y = h[na - 1][na - 1];
+ w = h[en - 1][na - 1] * h[na - 1][en - 1];
+ p = (y - x) / 2.0;
+ q = p * p + w;
+ z = Math.sqrt(Math.abs(q));
+ h[en - 1][en - 1] = x + t;
+ x = h[en - 1][en - 1];
+ h[na - 1][na - 1] = y + t;
+ if (q >= 0.0) { /* real pair */
+ if (p < 0.0) { /* sign */
+ z = p - Math.abs(z);
+ } else {
+ z = p + Math.abs(z);
+ }
+ wr[na - 1] = x + z;
+ wr[en - 1] = wr[na - 1];
+ if (z != 0.0) {
+ wr[en - 1] = x - w / z;
+ }
+ wi[na - 1] = 0.0;
+ wi[en - 1] = 0.0;
+ x = h[en - 1][na - 1];
+ s = Math.abs(x) + Math.abs(z);
+ p = x / s;
+ q = z / s;
+ r = Math.sqrt(p * p + q * q);
+ p /= r;
+ q /= r;
+ for (j = na - 1; j < n; j++) { /* row modification */
+ z = h[na - 1][j];
+ h[na - 1][j] = q * z + p * h[en - 1][j];
+ h[en - 1][j] = q * h[en - 1][j] - p * z;
+ }
+ for (i = 0; i < en; i++) { /* column modification */
+ z = h[i][na - 1];
+ h[i][na - 1] = q * z + p * h[i][en - 1];
+ h[i][en - 1] = q * h[i][en - 1] - p * z;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++) {
+ z = zz[i][na - 1];
+ zz[i][na - 1] = q * z + p * zz[i][en - 1];
+ zz[i][en - 1] = q * zz[i][en - 1] - p * z;
+ }
+ } else { /* complex pair */
+ wr[na - 1] = x + p;
+ wr[en - 1] = x + p;
+ wi[na - 1] = z;
+ wi[en - 1] = -z;
+ }
+ en -= 2;
+ } /* while en >= low */
+ /* backsubstitute to find vectors of upper triangular form */
+ if (norm != 0.0) {
+ for (en = n; en >= 1; en--) {
+ p = wr[en - 1];
+ q = wi[en - 1];
+ na = en - 1;
+ if (q == 0.0) {/* real vector */
+ m = en;
+ h[en - 1][en - 1] = 1.0;
+ if (na != 0) {
+ for (i = en - 2; i >= 0; i--) {
+ w = h[i][i] - p;
+ r = 0.0;
+ for (j = m - 1; j < en; j++) {
+ r += h[i][j] * h[j][en - 1];
+ }
+ if (wi[i] < 0.0) {
+ z = w;
+ s = r;
+ } else {
+ m = i + 1;
+ if (wi[i] == 0.0) {
+ t = w;
+ if (t == 0.0) {
+ tst1 = norm;
+ t = tst1;
+ do
+ {
+ t = 0.01 * t;
+ tst2 = norm + t;
+ }
+ while (tst2 > tst1);
+ }
+ h[i][en - 1] = -(r / t);
+ } else { /* solve real equations */
+ x = h[i][i + 1];
+ y = h[i + 1][i];
+ q = (wr[i] - p) * (wr[i] - p) + wi[i] * wi[i];
+ t = (x * s - z * r) / q;
+ h[i][en - 1] = t;
+ if (Math.abs(x) > Math.abs(z))
+ h[i + 1][en - 1] = (-r - w * t) / x;
+ else
+ h[i + 1][en - 1] = (-s - y * t) / z;
+ }
+ /* overflow control */
+ t = Math.abs(h[i][en - 1]);
+ if (t != 0.0) {
+ tst1 = t;
+ tst2 = tst1 + 1.0 / tst1;
+ if (tst2 <= tst1) {
+ for (j = i; j < en; j++) {
+ h[j][en - 1] /= t;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (q > 0.0) {
+ m = na;
+ if (Math.abs(h[en - 1][na - 1]) > Math.abs(h[na - 1][en - 1])) {
+ h[na - 1][na - 1] = q / h[en - 1][na - 1];
+ h[na - 1][en - 1] = (p - h[en - 1][en - 1])/h[en - 1][na - 1];
+ } else {
+ mcdiv(0.0, -h[na - 1][en - 1], h[na - 1][na - 1] - p, q);
+ h[na - 1][na - 1] = cr;
+ h[na - 1][en - 1] = ci;
+ }
+ h[en - 1][na - 1] = 0.0;
+ h[en - 1][en - 1] = 1.0;
+ if (en != 2) {
+ for (i = en - 3; i >= 0; i--) {
+ w = h[i][i] - p;
+ ra = 0.0;
+ sa = 0.0;
+ for (j = m - 1; j < en; j++) {
+ ra += h[i][j] * h[j][na - 1];
+ sa += h[i][j] * h[j][en - 1];
+ }
+ if (wi[i] < 0.0) {
+ z = w;
+ r = ra;
+ s = sa;
+ } else {
+ m = i + 1;
+ if (wi[i] == 0.0) {
+ mcdiv(-ra, -sa, w, q);
+ h[i][na - 1] = cr;
+ h[i][en - 1] = ci;
+ } else { /* solve complex equations */
+ x = h[i][i + 1];
+ y = h[i + 1][i];
+ vr = (wr[i] - p) * (wr[i] - p);
+ vr = vr + wi[i] * wi[i] - q * q;
+ vi = (wr[i] - p) * 2.0 * q;
+ if (vr == 0.0 && vi == 0.0) {
+ tst1 = norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) +
+ Math.abs(y) + Math.abs(z));
+ vr = tst1;
+ do
+ {
+ vr = 0.01 * vr;
+ tst2 = tst1 + vr;
+ }
+ while (tst2 > tst1);
+ }
+ mcdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
+ h[i][na - 1] = cr;
+ h[i][en - 1] = ci;
+ if (Math.abs(x) > Math.abs(z) + Math.abs(q)) {
+ h[i + 1]
+ [na - 1] = (q * h[i][en - 1] -
+ w * h[i][na - 1] - ra) / x;
+ h[i + 1][en - 1] = (-sa - w * h[i][en - 1] -
+ q * h[i][na - 1]) / x;
+ } else {
+ mcdiv(-r - y * h[i][na - 1], -s - y * h[i][en - 1], z, q);
+ h[i + 1][na - 1] = cr;
+ h[i + 1][en - 1] = ci;
+ }
+ }
+ /* overflow control */
+ t = (Math.abs(h[i][na - 1]) > Math.abs(h[i][en - 1])) ?
+ Math.abs(h[i][na - 1]) : Math.abs(h[i][en - 1]);
+ if (t != 0.0) {
+ tst1 = t;
+ tst2 = tst1 + 1.0 / tst1;
+ if (tst2 <= tst1) {
+ for (j = i; j < en; j++) {
+ h[j][na - 1] /= t;
+ h[j][en - 1] /= t;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ /* end back substitution. vectors of isolated roots */
+ for (i = 0; i < n; i++) {
+ if (i + 1 < low || i + 1 > hgh) {
+ for (j = i; j < n; j++) {
+ zz[i][j] = h[i][j];
+ }
+ }
+ }
+ /* multiply by transformation matrix to give vectors of
+ * original full matrix. */
+ for (j = n - 1; j >= low - 1; j--) {
+ m = ((j + 1) < hgh) ? (j + 1) : hgh; /* min */
+ for (i = low - 1; i < hgh; i++) {
+ z = 0.0;
+ for (k = low - 1; k < m; k++) {
+ z += zz[i][k] * h[k][j];
+ }
+ zz[i][j] = z;
+ }
+ }
+ }
+ }
+
+ private void eltran(double[][] a, double[][] zz, int[] ordr, int n) {
+ int i, j, m;
+
+ for (i = 0; i < n; i++) {
+ for (j = i + 1; j < n; j++) {
+ zz[i][j] = 0.0;
+ zz[j][i] = 0.0;
+ }
+ zz[i][i] = 1.0;
+ }
+ if (n <= 2) {
+ return;
+ }
+ for (m = n - 1; m >= 2; m--) {
+ for (i = m; i < n; i++) {
+ zz[i][m - 1] = a[i][m - 2];
+ }
+ i = ordr[m - 1];
+ if (i != m) {
+ for (j = m - 1; j < n; j++) {
+ zz[m - 1][j] = zz[i - 1][j];
+ zz[i - 1][j] = 0.0;
+ }
+ zz[i - 1][m - 1] = 1.0;
+ }
+ }
+ }
+
+ void luinverse(double[][] inmat, double[][] imtrx, int size) throws IllegalArgumentException {
+ int i, j, k, l, maxi=0, idx, ix, jx;
+ double sum, tmp, maxb, aw;
+ int[] index;
+ double[] wk;
+ double[][] omtrx;
+
+
+ index = new int[size];
+ omtrx = new double[size][size];
+
+ /* copy inmat to omtrx */
+ for (i = 0; i < size; i++) {
+ for (j = 0; j < size; j++) {
+ omtrx[i][j] = inmat[i][j];
+ }
+ }
+
+ wk = new double[size];
+ aw = 1.0;
+ for (i = 0; i < size; i++) {
+ maxb = 0.0;
+ for (j = 0; j < size; j++) {
+ if (Math.abs(omtrx[i][j]) > maxb) {
+ maxb = Math.abs(omtrx[i][j]);
+ }
+ }
+ if (maxb == 0.0) {
+ /* Singular matrix */
+ System.out.println("Singular matrix encountered");
+ throw new IllegalArgumentException("Singular matrix");
+ }
+ wk[i] = 1.0/maxb;
+ }
+ for (j = 0; j < size; j++) {
+ for (i = 0; i < j; i++) {
+ sum = omtrx[i][j];
+ for (k = 0; k < i; k++) {
+ sum -= omtrx[i][k] * omtrx[k][j];
+ }
+ omtrx[i][j] = sum;
+ }
+ maxb = 0.0;
+ for (i = j; i < size; i++) {
+ sum = omtrx[i][j];
+ for (k = 0; k < j; k++) {
+ sum -= omtrx[i][k] * omtrx[k][j];
+ }
+ omtrx[i][j] = sum;
+ tmp = wk[i] * Math.abs(sum);
+ if (tmp >= maxb) {
+ maxb = tmp;
+ maxi = i;
+ }
+ }
+ if (j != maxi) {
+ for (k = 0; k < size; k++) {
+ tmp = omtrx[maxi][k];
+ omtrx[maxi][k] = omtrx[j][k];
+ omtrx[j][k] = tmp;
+ }
+ aw = -aw;
+ wk[maxi] = wk[j];
+ }
+ index[j] = maxi;
+ if (omtrx[j][j] == 0.0) {
+ omtrx[j][j] = EPSILON;
+ }
+ if (j != size - 1) {
+ tmp = 1.0 / omtrx[j][j];
+ for (i = j + 1; i < size; i++) {
+ omtrx[i][j] *= tmp;
+ }
+ }
+ }
+ for (jx = 0; jx < size; jx++) {
+ for (ix = 0; ix < size; ix++) {
+ wk[ix] = 0.0;
+ }
+ wk[jx] = 1.0;
+ l = -1;
+ for (i = 0; i < size; i++) {
+ idx = index[i];
+ sum = wk[idx];
+ wk[idx] = wk[i];
+ if (l != -1) {
+ for (j = l; j < i; j++) {
+ sum -= omtrx[i][j] * wk[j];
+ }
+ } else if (sum != 0.0) {
+ l = i;
+ }
+ wk[i] = sum;
+ }
+ for (i = size - 1; i >= 0; i--) {
+ sum = wk[i];
+ for (j = i + 1; j < size; j++) {
+ sum -= omtrx[i][j] * wk[j];
+ }
+ wk[i] = sum/omtrx[i][i];
+ }
+ for (ix = 0; ix < size; ix++) {
+ imtrx[ix][jx] = wk[ix];
+ }
+ }
+ wk = null;
+ index = null;
+ omtrx = null;
+ }
+ public static final void copy(double[][] source, double[][] dest) {
+ for(int i = 0 ; i < source.length ; i++) {
+ System.arraycopy(source[i],0,dest[i],0,source[i].length);
+ }
+ }
+}
diff --git a/src/jade/reconstruct/area/P.java b/src/jade/reconstruct/area/P.java
new file mode 100644
index 0000000..0b15065
--- /dev/null
+++ b/src/jade/reconstruct/area/P.java
@@ -0,0 +1,35 @@
+/*
+ * P.java
+ *
+ * Created on April 11, 2005, 9:30 PM
+ */
+
+package jade.reconstruct.area;
+
+
+/**
+ *
+ * @author stephensmith
+ */
+public class P {
+
+ /** Creates a new instance of P */
+ public P(Q qin) {
+ q=qin;
+ me = new MatrixExponential(q.getQ().length);
+ me.updateByRelativeRates(q.getQ());
+ }
+
+ public double getRateChangeProbability(int row, int column){
+ return me.getTransitionProbability(row,column);
+ }
+
+ public void setBL(double b){
+ me.setDistance(b);
+ }
+
+ //private double [][]arr;
+ //private double bl;
+ private Q q;
+ private MatrixExponential me;
+}
diff --git a/src/jade/reconstruct/area/PJAMA.java b/src/jade/reconstruct/area/PJAMA.java
new file mode 100644
index 0000000..b21d146
--- /dev/null
+++ b/src/jade/reconstruct/area/PJAMA.java
@@ -0,0 +1,100 @@
+package jade.reconstruct.area;
+
+import java.text.*;
+//import org.netlib.lapack.*;
+//import org.netlib.util.intW;
+
+//import Jama.*;
+
+
+public class PJAMA {
+ public PJAMA(Q qin) {
+ q=qin;
+ //me = new MatrixExponential(q.getQ().length);
+ //me.updateByRelativeRates(q.getQ());
+ }
+
+ public double getRateChangeProbability(int row, int column){
+ //return me.getTransitionProbability(row, column);
+ return P[row][column];
+ }
+
+ public void setBL(double b){
+ //me.setDistance(b);
+
+ PJNI pjni = new PJNI();
+ double [] tq = transform2d(q.getQ(), b);
+ double [] tq2 = pjni.matrixExp(tq, q.getQ().length);
+ P = transform1d(tq2);
+ }
+
+ private double [] transform2d(double [][] inm, double b){
+ double [] ret = new double [inm.length*inm.length];
+ int x = 0;
+ for(int i=0;i<inm.length;i++){
+ for(int j=0;j<inm.length;j++){
+ ret[x] = inm[i][j]*b;
+ x++;
+ }
+ }
+ return ret;
+ }
+
+ private double [][] transform1d(double [] inm){
+ double [][] ret = new double [q.getQ().length][q.getQ().length];
+ int x = 0;
+ for(int i=0;i<q.getQ().length;i++){
+ for(int j=0;j<q.getQ().length;j++){
+ ret[i][j] = inm[x];
+ x++;
+ }
+ }
+ return ret;
+ }
+
+ public void setBLOLD(double b){/*
+ double [][] qm = q.getQ();
+ double [] eigva= new double [qm.length];
+ double [] im_eigva = new double [qm.length];
+ double [][] l_eivec = new double [qm.length][qm.length];
+ double [][] r_eivec=new double [qm.length][qm.length];
+ intW info = new intW(0);
+ double [] work = new double [10*qm.length];
+ DGEEV.DGEEV("V","V",qm.length, qm, eigva, im_eigva,
+ l_eivec, r_eivec, work, 10*qm.length, info);
+ System.out.println(jade.math.Utils.printDVec(eigva));
+ System.out.println(jade.math.Utils.printDMat(r_eivec));
+ for(int i=0;i<eigva.length;i++){eigva[i] = Math.exp(eigva[i]*b);}
+ double [][] exp_D = Matrix.identity(q.getQ().length, q.getQ().length).getArrayCopy();
+ for(int i=0;i<exp_D.length;i++){
+ for(int j=0;j<exp_D[i].length;j++){
+ if(j==i)
+ exp_D[i][j] =eigva[i];
+ else
+ exp_D[i][j] = 0.0;
+ }
+ }
+ System.out.println(jade.math.Utils.printDMat(exp_D));
+ double [][] C_inv = new Matrix(r_eivec).inverse().getArrayCopy();
+ double [][] P = new double [r_eivec.length][r_eivec.length];
+ for(int i=0;i<P.length;i++){
+ for(int j=0;j<P[i].length;j++){
+ double s = r_eivec[i][j] * exp_D[j][j];
+ P[i][j] = s;
+ }
+ }
+ System.out.println(jade.math.Utils.printDMat(P));
+ System.out.println(jade.math.Utils.printDMat(C_inv));*/
+ //P = dot(dot(C, exp_D), C_inv)
+ //Psum = scipy.sum(P,1) # sum across rows, and
+ //P = P/Psum # divide by sum so that all elements are between 0 and 1
+ //return P
+ }
+
+ //private double [][]arr;
+ //private double bl;
+ private Q q;
+ private MatrixExponential me;
+ private double [][] P;
+ //private Matrix P;
+}
diff --git a/src/jade/reconstruct/area/PJNI.java b/src/jade/reconstruct/area/PJNI.java
new file mode 100644
index 0000000..0524981
--- /dev/null
+++ b/src/jade/reconstruct/area/PJNI.java
@@ -0,0 +1,8 @@
+package jade.reconstruct.area;
+
+public class PJNI {
+ public native double [] matrixExp(double [] mat, int size);
+ static {
+ System.loadLibrary("matrixExp");
+ }
+}
diff --git a/src/jade/reconstruct/area/Q.java b/src/jade/reconstruct/area/Q.java
new file mode 100644
index 0000000..5885bab
--- /dev/null
+++ b/src/jade/reconstruct/area/Q.java
@@ -0,0 +1,93 @@
+/*
+ * Q.java
+ *
+ * Created on April 11, 2005, 9:30 PM
+ */
+
+package jade.reconstruct.area;
+
+
+/**
+ *
+ * @author stephensmith
+ */
+public class Q {
+
+ /** Creates a new instance of Q */
+ public Q(double [][] inr) {
+ arr=inr;//
+ //doSum();
+ //scale();
+ setI();//
+
+ }
+ //do before scale
+ private void doSum(){
+ sum=0;
+ for(int i=0;i<arr.length;i++){
+ for(int j=0;j<arr.length;j++){
+ if(i==j){
+ }else{
+ sum=sum+arr[i][j];
+ }
+ }
+ }
+ scale = 1*sum;
+ //System.out.println("sum "+sum+" scale "+scale);
+ }
+ //check divide multiply
+ private void scale(){
+ for(int i=0;i<arr.length;i++){
+ for(int j=0;j<arr.length;j++){
+ arr[i][j]=arr[i][j]/scale;
+ }
+ }
+ setI();
+ }
+
+ private void setI(){
+ double [] rows = new double [arr.length];
+ for(int i=0;i<arr.length;i++){
+ rows[i]=0;
+ for(int j=0;j<arr.length;j++){
+ if(i!=j){
+ rows[i]=rows[i]+arr[i][j];
+ }
+ }
+ }
+ for(int i=0;i<arr.length;i++){
+ for(int j=0;j<arr.length;j++){
+ if(i==j){
+ arr[i][j]=0-rows[i];
+ }
+ }
+ }
+ //
+ //added dont know
+ for(int i=0;i<arr.length;i++){
+ for(int j=0;j<arr.length;j++){
+ arr[i][j]=arr[i][j];
+ }
+ }
+ //
+ //
+ }
+
+ public double [][] getQ(){
+ return arr;
+ }
+
+ public void printQ(){
+ for(int i=0;i<arr.length;i++){
+ for(int j=0;j<arr.length;j++){
+ System.out.print(arr[i][j]+" ");
+ }
+ System.out.println();
+ }
+ }
+
+ //private int dimensions;
+ private double [][]arr;
+ private double sum;
+ private double scale;
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizer.java b/src/jade/reconstruct/area/RangeReconstructionOptimizer.java
new file mode 100644
index 0000000..77e114b
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizer.java
@@ -0,0 +1,67 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizer implements AP_praxis_method{
+ private RangeReconstructor rec;
+
+ private double disp;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizer(RangeReconstructor rec){
+ this.rec = rec;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global dispersal
+ * x[1] = global dispersal
+ * x[2] = global extinction
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[1]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[1] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[2]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[2] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ rec.set_dipsersal(x[1]);
+ disp = x[1];
+ rec.set_extinction(x[2]);
+ ext = x[2];
+ rec.set_ratemodel();
+ try{
+ lh = -Math.log(rec.eval_likelihood());
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+
+ public double get_disp(){return this.disp;}
+ public double get_ext(){return this.ext;}
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizerDispersal.java b/src/jade/reconstruct/area/RangeReconstructionOptimizerDispersal.java
new file mode 100644
index 0000000..8e6b374
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizerDispersal.java
@@ -0,0 +1,107 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+import java.util.*;
+
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizerDispersal implements AP_praxis_method{
+ private RangeReconstructor rec;
+
+ //private double disp;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+ private ArrayList<ArrayList<Double>> d0 = new ArrayList<ArrayList<Double>>();
+ private ArrayList<ArrayList<Double>> d1 = new ArrayList<ArrayList<Double>>();
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizerDispersal(RangeReconstructor rec){
+ this.rec = rec;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global extinction
+ * x[1] = dispersal
+ * x[2] = etc
+ * ...
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ for(int i=1; i < x.length ; i++){
+ try{
+ BigDecimal bd1 = new BigDecimal(x[i]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[i] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ }
+ rec.set_extinction(x[1]);
+ //rec.set_dipsersal(x[1]);
+ //disp = x[1];
+ ext = x[1];
+ /*
+ * adding to rate matrix should look like
+ * 1 | 2 | 3
+ * =========|=======|=========
+ * 1| | 1 | 2
+ * -|-------|-------|--------
+ * 2| 3 | | 4
+ * -|-------|-------|--------
+ * 3| 5 | 6 |
+ *
+ */
+ d0 = new ArrayList<ArrayList<Double>>();
+ int curpara = 2;
+ for(int i=0;i<rec.get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<rec.get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d0.add(td);
+ }
+ //1 period
+ d1 = new ArrayList<ArrayList<Double>>();
+ for(int i=0;i<rec.get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<rec.get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d1.add(td);
+ }
+ //for(int i=2;i<=n;i++){e.add(x[i]);}
+ rec.set_dispersalmatrix(d0,0);
+ rec.set_dispersalmatrix(d1,1);
+ //ext = x[2];
+ rec.set_ratemodel();
+ try{
+ lh = -Math.log(rec.eval_likelihood());
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizerLocalExtinction.java b/src/jade/reconstruct/area/RangeReconstructionOptimizerLocalExtinction.java
new file mode 100644
index 0000000..aba817e
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizerLocalExtinction.java
@@ -0,0 +1,71 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+import java.util.*;
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizerLocalExtinction implements AP_praxis_method{
+ private RangeReconstructor rec;
+
+ private double disp;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+ private ArrayList<Double> e = new ArrayList<Double>();
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizerLocalExtinction(RangeReconstructor rec){
+ this.rec = rec;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global dispersal
+ * x[1] = global dispersal
+ * x[2] = global extinction
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[1]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[1] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[2]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[2] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ rec.set_dipsersal(x[1]);
+ disp = x[1];
+ //rec.set_extinction(x[2]);
+ e = new ArrayList<Double>();
+ for(int i=2;i<=n;i++){e.add(x[i]);}
+ rec.set_localextinction(e);
+ //ext = x[2];
+ rec.set_ratemodel();
+ try{
+ lh = -Math.log(rec.eval_likelihood());
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+
+ public double get_disp(){return this.disp;}
+ public double get_ext(){return this.ext;}
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizerMult.java b/src/jade/reconstruct/area/RangeReconstructionOptimizerMult.java
new file mode 100644
index 0000000..bbc9855
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizerMult.java
@@ -0,0 +1,70 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+import java.util.*;
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizerMult implements AP_praxis_method{
+ private ArrayList<RangeReconstructor> recs;
+
+ private double disp;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizerMult(ArrayList<RangeReconstructor> recs){
+ this.recs = recs;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global dispersal
+ * x[1] = global dispersal
+ * x[2] = global extinction
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[1]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[1] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[2]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[2] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ disp = x[1];
+ ext = x[2];
+ try{
+ lh = 1;
+ for(int i=0;i<recs.size();i++){
+ recs.get(i).set_dipsersal(x[1]);
+ recs.get(i).set_extinction(x[2]);
+ recs.get(i).set_ratemodel();
+ lh += -Math.log(recs.get(i).eval_likelihood());
+ }
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+
+ public double get_disp(){return this.disp;}
+ public double get_ext(){return this.ext;}
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersal.java b/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersal.java
new file mode 100644
index 0000000..3976467
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersal.java
@@ -0,0 +1,91 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+import java.util.*;
+
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizerMultDispersal implements AP_praxis_method{
+ private ArrayList<RangeReconstructor> recs;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+ private ArrayList<ArrayList<Double>> d = new ArrayList<ArrayList<Double>>();
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizerMultDispersal(ArrayList<RangeReconstructor> recs){
+ this.recs = recs;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global dispersal
+ * x[1] = global dispersal
+ * x[2] = global extinction
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[1]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[1] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[2]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[2] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ /*
+ * adding to rate matrix should look like
+ * 1 | 2 | 3
+ * =========|=======|=========
+ * 1| | 1 | 2
+ * -|-------|-------|--------
+ * 2| 3 | | 4
+ * -|-------|-------|--------
+ * 3| 5 | 6 |
+ *
+ */
+ d = new ArrayList<ArrayList<Double>>();
+ int curpara = 2;
+
+ for(int i=0;i<recs.get(i).get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<recs.get(i).get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d.add(td);
+ }
+ try{
+ lh = 1;
+ for(int i=0;i<recs.size();i++){
+ recs.get(i).set_dispersalmatrix(d,0);
+ recs.get(i).set_extinction(x[1]);
+ recs.get(i).set_ratemodel();
+ lh += -Math.log(recs.get(i).eval_likelihood());
+ }
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersalLB.java b/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersalLB.java
new file mode 100644
index 0000000..673d55f
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructionOptimizerMultDispersalLB.java
@@ -0,0 +1,123 @@
+package jade.reconstruct.area;
+import java.math.BigDecimal;
+import java.util.*;
+
+import jade.math.*;
+import jade.tree.*;
+import jade.data.*;
+
+public class RangeReconstructionOptimizerMultDispersalLB implements AP_praxis_method{
+ private ArrayList<RangeReconstructor> recs;
+
+ private double LARGE = 100000;
+ private double PMAX = 10.0;
+ private double ext;
+ private ArrayList<ArrayList<Double>> d0 = new ArrayList<ArrayList<Double>>();
+ private ArrayList<ArrayList<Double>> d1 = new ArrayList<ArrayList<Double>>();
+ //private ArrayList<ArrayList<Double>> d2 = new ArrayList<ArrayList<Double>>();
+
+ int decimalPlace = 10;
+
+ public RangeReconstructionOptimizerMultDispersalLB(ArrayList<RangeReconstructor> recs){
+ this.recs = recs;
+ }
+
+ /*
+ * n = the number of variables
+ * x[0] = global dispersal
+ * x[1] = global dispersal
+ * x[2] = global extinction
+ * @see jade.math.AP_praxis_method#funct(int, double[])
+ */
+ public double funct(int n, double x[]){
+ double lh = 0.0;
+ for(int i=1 ; i<x.length; i++){
+ if(x[i] <= 0|| x[i] > PMAX)
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[1]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[1] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ try{
+ BigDecimal bd1 = new BigDecimal(x[2]);
+ bd1 = bd1.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
+ x[2] = bd1.doubleValue();
+ }catch(java.lang.NumberFormatException nfe){
+ return LARGE;
+ }
+ /*
+ * adding to rate matrix should look like
+ * 1 | 2 | 3
+ * =========|=======|=========
+ * 1| | 1 | 2
+ * -|-------|-------|--------
+ * 2| 3 | | 4
+ * -|-------|-------|--------
+ * 3| 5 | 6 |
+ *
+ */
+ //0 period
+ d0 = new ArrayList<ArrayList<Double>>();
+ int curpara = 2;
+ for(int i=0;i<recs.get(i).get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<recs.get(i).get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d0.add(td);
+ }
+ //1 period
+ d1 = new ArrayList<ArrayList<Double>>();
+ for(int i=0;i<recs.get(i).get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<recs.get(i).get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d1.add(td);
+ }
+ //2 period
+ /*d2 = new ArrayList<ArrayList<Double>>();
+ for(int i=0;i<recs.get(i).get_num_areas();i++){
+ ArrayList<Double> td = new ArrayList<Double>();
+ for(int j=0;j<recs.get(i).get_num_areas();j++){
+ if(i == j){
+ td.add(0.0);
+ }else{
+ td.add(x[curpara]);
+ curpara++;
+ }
+ }
+ d2.add(td);
+ }*/
+ try{
+ lh = 1;
+ for(int i=0;i<recs.size();i++){
+ recs.get(i).set_dispersalmatrix(d0,0);
+ recs.get(i).set_dispersalmatrix(d1,1);
+ //recs.get(i).set_dispersalmatrix(d2,2);
+ recs.get(i).set_extinction(x[1]);
+ recs.get(i).set_ratemodel();
+ lh += -Math.log(recs.get(i).eval_likelihood());
+ }
+ }catch(java.lang.ArithmeticException ae){
+ return LARGE;
+ }
+ if(lh < 0 )
+ return LARGE;
+ return lh;
+ }
+}
diff --git a/src/jade/reconstruct/area/RangeReconstructor.java b/src/jade/reconstruct/area/RangeReconstructor.java
new file mode 100644
index 0000000..547be39
--- /dev/null
+++ b/src/jade/reconstruct/area/RangeReconstructor.java
@@ -0,0 +1,11 @@
+package jade.reconstruct.area;
+import java.util.*;
+public interface RangeReconstructor {
+ public void set_dipsersal(double x);
+ public void set_extinction(double x);
+ public void set_localextinction(ArrayList<Double> e);
+ public void set_dispersalmatrix(ArrayList<ArrayList<Double>> d, int period);
+ public void set_ratemodel();
+ public double eval_likelihood();
+ public int get_num_areas();
+}
diff --git a/src/jade/reconstruct/area/RateModel.java b/src/jade/reconstruct/area/RateModel.java
new file mode 100644
index 0000000..8258936
--- /dev/null
+++ b/src/jade/reconstruct/area/RateModel.java
@@ -0,0 +1,271 @@
+package jade.reconstruct.area;
+
+import java.util.*;
+
+public class RateModel {
+ private ArrayList<Double> periods;//periods should be from old to young
+ private ArrayList<Area> areas;
+ private int [][] dists;
+ private ArrayList<double [][]> D;
+ private ArrayList<double [][]> Dm;//dispersal mask
+ private ArrayList<double []> E;
+ private ArrayList<double [][]> Q;
+ private double dispersal = 0.01;
+ private double extinction = 0.01;
+ private ArrayList<Double> e;
+ private ArrayList<ArrayList<ArrayList<Double>>> d = new ArrayList<ArrayList<ArrayList<Double>>> (); //period, row, column
+ private boolean localextinction = false;
+ private HashMap<String,Integer > dist_dict;
+ private boolean GE = false;
+
+ public RateModel(ArrayList<Area> areas, ArrayList<Double>periods, boolean GE){
+ this.areas = areas;
+ this.periods = periods;
+ dists = jade.math.NChooseM.iterate_all_bv2small(areas.size());
+ dist_dict = new HashMap< String, Integer >();
+ if(GE == true){
+ this.GE = true;
+ int [] ex = new int [this.areas.size()];
+ for(int i = 0 ;i < ex.length; i++){ex[i] = 0;}
+ int [][] tdists = new int [dists.length+1][this.areas.size()];
+ tdists[0] = ex;
+ for(int i = 1; i < dists.length+1; i++){tdists[i] = dists[i-1];}
+ dists = tdists;
+ }
+ for(int i=0;i<dists.length;i++){
+ String x = "";
+ for(int j=0;j<dists[i].length;j++){x += String.valueOf(dists[i][j]);}
+ dist_dict.put(x, i);
+ //System.out.println(x);
+ }
+ setup_Dm();
+ }
+
+ public void set_dispersal(double d){this.dispersal = d;}
+ public void set_extinction(double e){this.extinction = e;}
+ /*
+ * area specific extinction
+ */
+ public void set_localextinction(ArrayList<Double> e){this.e = e;localextinction = true;}
+
+ /*
+ * more complex dispersal matrix
+ */
+ private boolean compdispersalmatrix = false;
+ public void set_dispersalmatrix(ArrayList<ArrayList<Double>> d, int period){
+ if (this.d.size()<=period || this.d.size() == 0){
+ this.d.add(d);
+ }else{
+ this.d.set(period, d);
+ }
+ compdispersalmatrix = true;
+ }
+
+
+ public void setup(boolean verbose){
+ setup_D();
+ if(localextinction == false)
+ setup_E();
+ else{
+ setup_E(e);
+ }
+ setup_Q();
+ if(verbose == true){
+ for(int i=0;i<this.Dm.size();i++){
+ System.out.println(jade.math.Utils.printDMat(this.Dm.get(i)));
+ }
+ for(int i=0;i<this.D.size();i++){
+ System.out.println(jade.math.Utils.printDMat(this.D.get(i)));
+ }
+ for(int i=0;i<this.Q.size();i++){
+ System.out.println(jade.math.Utils.printDMat(this.Q.get(i)));
+ }
+ }
+ }
+
+ public ArrayList<Double> getPeriods(){return periods;}
+
+ public int getRelevantPeriod(double time){
+ int ret = 0;
+ /*
+ * period time is the start time (old time)
+ * for the period, the end time is the start time of the
+ * next younger period 0
+ */
+ int i = 0;
+ while(periods.get(i)> time){
+ ret = i;
+ i++;
+ if(i<periods.size()){
+ continue;
+ }else{
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /*
+ * return the distribution index for a range
+ */
+ public int get_distribution_index(int [] dist){
+ String x = "";
+ for(int j=0;j<dist.length;j++){x += String.valueOf(dist[j]);}
+ return dist_dict.get(x);
+ }
+ public int get_distribution_index(String dist){
+ return dist_dict.get(dist);
+ }
+
+ public double [][] P (int period, double time){
+ Q tq = new Q(Q.get(period));
+ PJAMA tp = new PJAMA(tq);
+ tp.setBL(time);
+ //System.out.println(time);
+ double [][] retp = new double[dists.length][dists.length];
+ for(int i=0;i<retp.length;i++){
+ for(int j=0;j<retp.length;j++){
+ retp[i][j] = tp.getRateChangeProbability(i, j);
+ }
+ }
+ //System.out.println(jade.math.Utils.printDMat(retp));
+ return retp;
+ }
+
+ public ArrayList<Area> get_areas(){return areas;}
+
+ public int [][] get_dists(){return dists;}
+
+ /*
+ * for dispersal difference
+ */
+ public void reset_Dm(){
+ setup_Dm();
+ }
+
+ public void set_local_dispersal(int period, int start, int end, double value,
+ boolean sym){
+ Dm.get(period)[start][end] = value;
+ if(sym == true){
+ Dm.get(period)[end][start] = value;
+ }
+ }
+ /*
+ * private methods
+ */
+ /*
+ * mask
+ */
+ private void setup_Dm(){
+ Dm = new ArrayList<double [][]>();
+ for(int i=0;i<periods.size();i++){
+ Dm.add(new double [areas.size()][areas.size()]);
+ for(int j=0;j<areas.size();j++){
+ for(int h=0;h<areas.size();h++){
+ Dm.get(i)[j][h] = 1.0;
+ }
+ }
+ }
+ }
+ private void setup_D(){
+ D = new ArrayList<double [][]>();
+ for(int i=0;i<periods.size();i++){
+ D.add(new double [areas.size()][areas.size()]);
+ for(int j=0;j<areas.size();j++){
+ for(int h=0;h<areas.size();h++){
+ if(this.compdispersalmatrix == false){
+ D.get(i)[j][h] = 1.0 * dispersal * Dm.get(i)[j][h];
+ }else{
+ D.get(i)[j][h] = 1.0 * d.get(i).get(j).get(h) * Dm.get(i)[j][h];
+ }
+ }
+ }
+ }
+ /*
+ * add some stuff
+ */
+ for(int p=0;p<periods.size();p++){
+ for(int a = 0;a<areas.size();a++){
+ this.D.get(p)[a][a] = 0.0;
+ }
+ }
+ }
+ /*
+ * E
+ */
+ private void setup_E(){
+ E = new ArrayList<double []>();
+ for(int i=0;i<periods.size();i++){
+ E.add(new double [areas.size()]);
+ for(int j=0;j<areas.size();j++){
+ E.get(i)[j] = 1.0*extinction ;
+ }
+ }
+ }
+ /*
+ * local extinction E
+ */
+ private void setup_E(ArrayList<Double> e){
+ E = new ArrayList<double []>();
+ for(int i=0;i<periods.size();i++){
+ E.add(new double [areas.size()]);
+ for(int j=0;j<areas.size();j++){
+ E.get(i)[j] = 1.0*e.get(j) ;
+ }
+ }
+ }
+ /*
+ * Q
+ */
+ private void setup_Q(){
+ Q = new ArrayList<double [][]>();
+ for(int i=0;i<periods.size();i++){
+ Q.add(new double [dists.length][dists.length]);
+ for(int j=0;j<areas.size();j++){
+ for(int h=0;h<areas.size();h++){
+ Q.get(i)[j][h] = 0.0;
+ }
+ }
+ }
+
+ for(int i=0;i<periods.size();i++){
+ for(int j=0;j<dists.length;j++){
+ int s1 = jade.math.Utils.sum(dists[j]);
+ if(s1 > 0){
+ for(int k=0;k<dists.length;k++){
+ int s2 = jade.math.Utils.sum(dists[k]);
+ int [] xor = jade.math.Utils.logical_xor_int(dists[j], dists[k]);
+ if(jade.math.Utils.sum(xor) == 1){
+ ArrayList<Integer> dest = jade.math.Utils.nonzero_int(xor);
+ double rate = 0.0;
+ if(s1 < s2){//dispersal
+ rate = 0.0;
+ ArrayList<Integer> src = jade.math.Utils.nonzero_int(dists[j]);
+ for(int x =0;x<src.size();x++){rate += this.D.get(i)[src.get(x)][dest.get(0)];}
+ }//if
+ else{//extinction
+ rate = this.E.get(i)[dest.get(0)];
+ }//else
+ this.Q.get(i)[j][k] = rate;
+ }//if
+ }//for
+ }//if
+ }//for
+ /*
+ * Q diagonal
+ */
+ for(int j=0;j<dists.length;j++){
+ this.Q.get(i)[j][j] = jade.math.Utils.sum(this.Q.get(i)[j]) - this.Q.get(i)[j][j] * -1.0;
+ }
+ }//for
+ }//private
+
+ /*
+ *
+ */
+
+
+ public static void main(String [] args){
+
+ }
+}
diff --git a/src/jade/reconstruct/discrete/MultiStateMarginalCalculator.java b/src/jade/reconstruct/discrete/MultiStateMarginalCalculator.java
new file mode 100644
index 0000000..7cafdb8
--- /dev/null
+++ b/src/jade/reconstruct/discrete/MultiStateMarginalCalculator.java
@@ -0,0 +1,201 @@
+package jade.reconstruct.discrete;
+
+import java.util.*;
+import java.text.*;
+import jade.tree.*;
+import jade.data.*;
+
+
+public class MultiStateMarginalCalculator {
+ private HashMap<Node, double[]> ltgn;
+
+ private HashMap<Node, double[]> conditionals;
+
+ private P p;
+
+ private AbstractAlignment aln;
+
+ private Tree tree;
+
+ private double rate;
+
+ NumberFormat df = new DecimalFormat("0.00000");
+
+ private boolean verbose;
+
+ private int size;
+
+ /** Creates a new instance of MultiStateMarginalCalculator */
+ public MultiStateMarginalCalculator(AbstractAlignment aln, Tree tree, int size,
+ double rate, boolean verbose) {
+ this.aln = aln;
+ this.tree = tree;
+ this.rate = rate;
+ this.p = new P(size, rate, rate);
+ this.size = size;
+ this.verbose = verbose;
+ // P.getP(0.1);
+ // P.printP();
+ conditionals = new HashMap<Node, double[]>();
+ ltgn = new HashMap<Node, double[]>();
+ }
+
+ public void setRate(double rate) {
+ this.rate = rate;
+ this.p = new P(size, rate, rate);
+ conditionals = new HashMap<Node, double[]>();
+ ltgn = new HashMap<Node, double[]>();
+ }
+
+ public double calculate() {
+ postOrder1(tree.getRoot());
+ preOrder(tree.getRoot());
+ double value = 0;
+ for (int i = 0; i < size; i++) {
+ value = value
+ + (conditionals.get(tree.getRoot())[i] * (1.0 / size));
+ }
+
+ // for(int i=0;i<tree.getInternalNodeCount();i++){
+ // double [] tv = ltgn.get(tree.getInternalNode(i));
+ // double [] prop = new double [2];
+ // prop[0] = tv[0]/(tv[0]+tv[1]);
+ // prop[1] = tv[1]/(tv[0]+tv[1]);
+ // tree.getInternalNode(i).setIdentifier(new
+ // Identifier(df.format(prop[0])+"_"+df.format(prop[1])));
+ // if(tree.getInternalNode(i)==tree.getRoot()&&verbose==true){
+ // System.out.println("root value:
+ // "+df.format((conditionals.get(tree.getRoot())[0]*0.5)/value)+"_"+df.format((conditionals.get(tree.getRoot())[1]*0.5)/value));
+ // }
+ // }
+ // if(verbose == true){
+ // TreeUtils tu = new TreeUtils();
+ // PrintWriter pw = new PrintWriter(System.out);
+ // tu.reportshort(tree,pw);
+ // pw.flush();
+ // System.out.println("likelihood at root
+ // "+Math.log((conditionals.get(tree.getRoot())[0]*0.5)+(conditionals.get(tree.getRoot())[1]*0.5)));
+ // }
+ System.out.println(-Math.log(value));
+ return -Math.log(value);
+ // System.out.println("likelihood at root
+ // "+((conditionals.get(tree.getRoot())[0]*0.5)/value));
+ // System.out.println("likelihood at root
+ // "+((conditionals.get(tree.getRoot())[1]*0.5)/value));
+ }
+
+ private void postOrder1(Node node) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ postOrder1(node.getChild(i));
+ }
+ if (node.isExternal()) {
+ String name = node.getName();
+ int alnn = 0;
+ for (int i = 0; i < aln.getSequenceCount(); i++) {
+ if (aln.getIdentifier(i).compareTo(name) == 0)
+ alnn = i;
+ }
+ int x = Integer.valueOf(String.valueOf(aln.getData(alnn, 0)))
+ .intValue();
+ double[] ar = new double[size];
+ ar[x] = 1.0;
+ for (int i = 0; i < size; i++) {
+ if (i != x)
+ ar[i] = 0.0;
+ }
+ conditionals.put(node, ar);
+ } else {
+ double[] ar = new double[size];
+ for (int k = 0; k < size; k++) {
+ double tempcon = 1;
+ for (int i = 0; i < node.getChildCount(); i++) {
+ double[][] p_ar = p.getP(node.getChild(i).getBL());
+ double[] tar = conditionals.get(node.getChild(i));
+ double temptempcon = 0;
+ for (int j = 0; j < size; j++) {
+ temptempcon = temptempcon + (tar[j] * p_ar[k][j]);
+ }
+ tempcon = tempcon * temptempcon;
+ }
+ ar[k] = tempcon;
+ }
+ conditionals.put(node, ar);
+ }
+ }
+
+ private void preOrder(Node node) {
+ if ((tree.getRoot() != node) && !node.isExternal()) {
+ double[][] p_ar = new double[0][0];
+ // conditional likelihood excluding X
+ double[] clex = calcExclude(node.getParent(), node);
+ // L(T|X)
+ p_ar = p.getP(node.getBL());
+ double[] x = new double[size];
+ for (int i = 0; i < size; i++) {
+ x[i] = conditionals.get(node)[i];
+ double t = 0;
+ for (int j = 0; j < size; j++) {
+ t = t + (p_ar[j][i] * clex[j]);
+ }
+ x[i] = x[i] * t;
+ }
+ ltgn.put(node, x);
+ }
+ if ((tree.getRoot() != node)) {
+ double[] x = new double[size];
+ for (int i = 0; i < size; i++) {
+ x[i] = 0.0;
+ }
+ ltgn.put(node, x);
+ }
+ for (int i = 0; i < node.getChildCount(); i++) {
+ preOrder(node.getChild(i));
+ }
+ }
+
+ private double[] calcExclude(Node node, Node excl) {
+ double[][] p_ar = new double[0][0];
+ double[] clex = new double[size];
+ for (int i = 0; i < size; i++) {
+ clex[i] = 1.0;
+ }
+ // conditional likelihood excluding X
+ for (int i = 0; i < node.getChildCount(); i++) {
+ if (node.getChild(i) != excl) {
+ p_ar = p.getP(node.getChild(i).getBL());
+ for (int j = 0; j < size; j++) {
+ double t = 0;
+ for (int k = 0; k < size; k++) {
+ t = t
+ + (conditionals.get(node.getChild(i))[k] * p_ar[j][k]);
+ }
+ clex[j] = clex[j] * t;
+ }
+
+ }
+ }
+ Node mother = node.getParent();
+ if (mother != null) {
+ for (int i = 0; i < mother.getChildCount(); i++) {
+ if (mother.getChild(i) == node) {
+ p_ar = p.getP(mother.getChild(i).getBL());
+ }
+ }
+ double[] clExclude = calcExclude(mother, node);
+ for (int j = 0; j < size; j++) {
+ double t = 0;
+ for (int k = 0; k < size; k++) {
+ t = t + (clExclude[k] * p_ar[j][k]);
+ }
+ clex[j] = clex[j] * t;
+ }
+ }
+
+ double[] d = clex;
+ return d;
+ }
+
+ public static void main(String[] args) {
+ //new MultiStateMarginalCalculator(4);
+ }
+}
diff --git a/src/jade/reconstruct/discrete/P.java b/src/jade/reconstruct/discrete/P.java
new file mode 100644
index 0000000..c047a8b
--- /dev/null
+++ b/src/jade/reconstruct/discrete/P.java
@@ -0,0 +1,75 @@
+package jade.reconstruct.discrete;
+
+/*
+ * currently does not take into account assymetrical rates
+ * to do so, just need to do something with b
+ */
+
+public class P {
+ public P(int size) {
+ this.size = size;
+ this.a = 0.1;
+ this.b = 0.1;
+
+ }
+
+ public P(int size, double a, double b) {
+ this.size = size;
+ this.a = a;
+ this.b = b;
+ System.out.println(size);
+ }
+
+ public double[][] getP(double time) {
+ double[][] p = new double[2][2];
+ p = new double[size][size];
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ if (i == j) {
+ p[i][j] = (1.0 / size)
+ + ((size - 1) / Double.valueOf(size))
+ * (Math.exp(-(size * a * time)));
+ } else {
+ p[i][j] = (1.0 / size) - (1.0 / size)
+ * (Math.exp(-(size * a * time)));
+ }
+ }
+ }
+ P = p;
+ return p;
+ }
+
+ public void setRates(double a) {
+ this.a = a;
+ this.b = a;
+ }
+
+ public void setRates(double a, double b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public void printP() {
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ System.out.print(P[i][j] + "\t");
+ }
+ System.out.println();
+ }
+
+ }
+
+ public static void main(String[] args) {
+ P p = new P(2);
+ p.getP(1);
+ p.printP();
+ }
+
+ private double[][] P;
+
+ private double a;
+
+ private double b;
+
+ private int size;
+}
diff --git a/src/jade/runners/ConcatMain.java b/src/jade/runners/ConcatMain.java
new file mode 100644
index 0000000..ef079ea
--- /dev/null
+++ b/src/jade/runners/ConcatMain.java
@@ -0,0 +1,31 @@
+package jade.runners;
+
+import java.util.*;
+
+import jade.data.*;
+
+public class ConcatMain {
+ public ConcatMain(String [] args){
+ if(args.length<1){
+ printInfo();
+ System.exit(0);
+ }
+ else{
+ ArrayList <String> files = new ArrayList<String>();
+ for(int i=0;i<args.length;i++){
+ files.add(args[i]);
+ }
+ ConcatenateAlignment ca = new ConcatenateAlignment(files, false, "");
+ }
+ }
+ private void printInfo(){
+ System.out.println("usage: java -jar concat file1 file2 ...");
+ System.out.println("concat v.01 by Stephen Smith");
+ System.out.println("\tthis assumes that the files are aligned fasta (like from muscle) \n" +
+ "\tand that the taxa names are equivalent between files\n" +
+ "\tand that each file is a different gene");
+ }
+ public static void main(String [] args){
+ new ConcatMain(args);
+ }
+}
diff --git a/src/jade/runners/GBParserMain.java b/src/jade/runners/GBParserMain.java
new file mode 100644
index 0000000..824e1ed
--- /dev/null
+++ b/src/jade/runners/GBParserMain.java
@@ -0,0 +1,60 @@
+package jade.runners;
+
+import java.util.ArrayList;
+import java.io.*;
+import jade.data.*;
+
+public class GBParserMain {
+ public GBParserMain(String []args){
+ if(args.length<2){
+ printInfo();
+ System.exit(0);
+ }
+ else{
+ try{
+ if(Integer.valueOf(args[0]) == 8){//formatting for bioorganizer requires a region definition
+ if(args.length==3){
+ GBParser ca = new GBParser(args[1], args[2]);
+ ArrayList<Sequence> seqs = ca.getSeqs();
+ for(int i=0;i<seqs.size();i++){
+ System.out.println(">"+seqs.get(i).getID());
+ System.out.println(seqs.get(i).getSeq());
+ System.out.println();
+ }
+ }else{
+ System.out.println("you need to use the format java -jar gbparser.jar 8 file region");
+ System.exit(0);
+ }
+ }else{
+ GBParser ca = new GBParser(args[1], Integer.valueOf(args[0]));
+ ArrayList<Sequence> seqs = ca.getSeqs();
+ for(int i=0;i<seqs.size();i++){
+ System.out.println(">"+seqs.get(i).getID());
+ System.out.println(seqs.get(i).getSeq());
+ System.out.println();
+ }
+ }
+ }catch(IOException ioe){}
+ }
+ }
+ private void printInfo(){
+ System.out.println("usage: java -jar gbparser.jar # file");
+ System.out.println("gbparser v.01 by Stephen Andrew Smith (blackrim.org)");
+ System.out.println("\tthis assumes that the file is a genbank fasta file\n" +
+ "\tthe numbers correspond to these options for the names\n" +
+ "\t1) gi number\n" +
+ "\t2) gb number\n" +
+ "\t3) taxon name\n" +
+ "\t4) taxon_name\n" +
+ "\t5) T_name\n" +
+ "\t6) taxon_name_ginumber\n" +
+ "\t7) taxon_name_gbnumber\n" +
+ "\t8) formatting for bioorganizer so type java -jar gbparser.jar 8 file regionname\n"+
+ "\n" +
+ "NOTE 1: if there are duplicate names, this will add numbers to the end\n" +
+ "NOTE 2: some genbank entries are poorly formed and therefore do not get formatted correctly here, so double check!");
+ }
+ public static void main(String [] args){
+ new GBParserMain(args);
+ }
+}
diff --git a/src/jade/simulate/Area.java b/src/jade/simulate/Area.java
new file mode 100644
index 0000000..5a1567b
--- /dev/null
+++ b/src/jade/simulate/Area.java
@@ -0,0 +1,133 @@
+package jade.simulate;
+/*
+ * this should house all the variables such as
+ * area specific extinction and connections
+ */
+import java.util.*;
+
+public class Area {
+ public Area(int index){
+ this.index = index;
+ con = new ArrayList<Connection>();
+ exFunc = new ArrayList<ExtinctionFunction>();
+ succdisps = new HashMap<Area,Integer> ();
+ }
+
+ public int getIndex(){return index;}
+ public void addConnection(Connection in){
+ con.add(in);
+ }
+ /*
+ * start should be older than end
+ */
+ public void addConnection(Area dest,double start, double end, double func){
+ Connection nc = new Connection(dest,start,end,func);
+ con.add(nc);
+ }
+ public void addExtinction(double start, double end, double func){
+ ExtinctionFunction xf = new ExtinctionFunction(start, end, func);
+ exFunc.add(xf);
+ }
+ /*
+ * functional functions
+ */
+ /**
+ *
+ * @return a relavent connection given the time
+ */
+ public Connection getRandomRelaventConnection(double time){
+ ArrayList<Connection> tempcons = new ArrayList<Connection>();
+ for(int i=0;i<con.size();i++){
+ double st = con.get(i).getStart();
+ double et = con.get(i).getEnd();
+ if(time<=st&&time>=et){
+ tempcons.add(con.get(i));
+ }
+ }
+ if (tempcons.size()==0){
+ //System.out.println("arg");
+ return null;
+ }
+ else{
+ int x = new Random().nextInt(tempcons.size());
+ //System.out.println(tempcons.size());
+ return tempcons.get(x);
+ }
+ }
+
+ public double getExtinctionRate(double time){
+ double retextrate = extrate;
+ for(int i=0;i<exFunc.size();i++){
+ if(time <= exFunc.get(i).getStartTime()&&time > exFunc.get(i).getEndTime()){
+ retextrate = exFunc.get(i).getFunction();
+ }
+ }
+ return retextrate;
+ }
+
+ /*
+ * for constrained, area specific extinction
+ */
+ public void setLocalExtinctionRate(double rate){
+ areaspecificextinction = rate;
+ }
+ public double getLocalExtinctionRate(){
+ return areaspecificextinction;
+ }
+ /*
+ * for constrained, area specific dispersal
+ * add from old to new
+ */
+ public void setPeriods(ArrayList<Double> periodtimes){
+ dispersalVectorStartTimes = periodtimes;
+ dispersalVector = new ArrayList<HashMap<Area,Double>>();
+ for(int i = 0; i < periodtimes.size(); i ++){
+ dispersalVector.add(new HashMap<Area, Double>());
+ }
+ }
+ public void setLocalDispersalRate(Area area, double rate, int period){
+ dispersalVector.get(period).put(area, rate);
+ }
+ public double getLocalDispersalRate(Area area, double time){
+ int per = 0;
+ if(dispersalVectorStartTimes.size()>1){
+ for(int i=0;i<dispersalVectorStartTimes.size();i++){
+ double st = dispersalVectorStartTimes.get(i);
+ double en = 0;
+ if((i+1)<dispersalVectorStartTimes.size()){
+ en = dispersalVectorStartTimes.get(i+1);
+ }
+ if(time <= st && time > en){
+ per = i;
+ break;
+ }else{
+ per++;
+ }
+ }
+ }
+ return dispersalVector.get(per).get(area);
+ }
+ public void addSuccDisp(Area area){
+ if(succdisps.containsKey(area)==false){
+ succdisps.put(area, 1);
+ }else{
+ succdisps.put(area, succdisps.get(area)+1);
+ }
+ }
+
+ public int getSuccDisp(Area area){
+ return succdisps.get(area);
+ }
+
+ /*
+ * private
+ */
+ private HashMap<Area,Integer> succdisps;
+ private double areaspecificextinction = 1;
+ private double extrate=1;
+ private ArrayList<Connection>con;
+ private ArrayList<ExtinctionFunction>exFunc;
+ private int index;
+ private ArrayList<HashMap<Area,Double>> dispersalVector;
+ private ArrayList<Double> dispersalVectorStartTimes;
+}
diff --git a/src/jade/simulate/BioGeoSim.java b/src/jade/simulate/BioGeoSim.java
new file mode 100644
index 0000000..2830d91
--- /dev/null
+++ b/src/jade/simulate/BioGeoSim.java
@@ -0,0 +1,13 @@
+package jade.simulate;
+
+public class BioGeoSim {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/src/jade/simulate/BioGeoTree.java b/src/jade/simulate/BioGeoTree.java
new file mode 100644
index 0000000..aff7245
--- /dev/null
+++ b/src/jade/simulate/BioGeoTree.java
@@ -0,0 +1,564 @@
+package jade.simulate;
+
+/*
+ * right now this should just include the model implemented by Ree et al. 2005
+ *
+ * need to fix the adding of areas to take effect at the end (the speciation event)
+ */
+
+import jade.tree.*;
+
+import java.util.*;
+import java.io.*;
+
+public class BioGeoTree {
+
+ private ArrayList<Node> extantNodes;
+
+ private static String BIRTHTIME = "birthtime";
+
+ private static String DEATHTIME = "deathtime";
+
+ private int failure = 0;
+
+ public double deathrate = 0.01;// in an area, which translates to
+
+ // extinction if they go extinct in an area
+
+ public double vag = 0.01;// birth rate in an area
+
+ private double relativedispersalrate = 0;
+
+ public double birthrate = 0.2;// speciation rate
+
+ private Tree finalT;
+
+ private Random ran;
+
+ private int numareas = 4;// reset when set model
+
+ /*
+ * all lineages
+ */
+ private ArrayList<Lineage> lineages;
+
+ /*
+ * extant lineages
+ */
+ private ArrayList<Lineage> tantlineages;
+
+ /*
+ * extinct EXTERNAL nodes
+ */
+ private ArrayList<Lineage> extinctlineages;
+
+ public double extantstop = 20;
+
+ private double timestop = 0;
+
+ private double sumrate = 1.0;
+
+ private double currenttime = 0.0;
+
+ private int curdead = 0;
+
+ /*
+ * stop the options for simulation
+ */
+ /*
+ * this options has to do with the final tree that is printed true will
+ * print the extinct species false will remove the extinct species
+ */
+ private boolean showExtinct = false;
+
+ /*
+ * used for the model
+ */
+ private ArrayList<Area> areas;
+
+ /*
+ * used for output file
+ */
+ private FileWriter fw;
+
+ private FileWriter fwt;
+
+ private FileWriter fwp;
+
+ private FileWriter fwc;
+
+ /*
+ * used to count the number of changes
+ */
+ private int ch;
+
+ public BioGeoTree() {
+ ran = new Random();
+ lineages = new ArrayList<Lineage>();
+ tantlineages = new ArrayList<Lineage>();
+ extinctlineages = new ArrayList<Lineage>();
+ extantNodes = new ArrayList<Node>();
+ }
+
+ /*
+ * takes in already set up areas, which should have already set up
+ * connections and extinction functions
+ */
+ public void setModel(ArrayList<Area> areas) {
+ this.areas = areas;
+ numareas = this.areas.size();
+ }
+
+ public void start() {
+ // setup parameters
+ sumrate = vag + deathrate;
+ relativedispersalrate = vag / sumrate;
+ // start
+ finalT = new Tree();
+ Node root = new Node();
+ finalT.addExternalNode(root);
+ Lineage rt = new Lineage(root, numareas);
+ rt.addArea(0);// rt.addArea(1);//rt.addArea(2);rt.addArea(3);
+ lineages.add(rt);
+ tantlineages.add(rt);
+ extantNodes.add(root);
+ // set the time at the beginning of the node
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ // set the name of the root node
+ root.setName("root");
+ root.setName(((Lineage) tantlineages.get(0)).getString());
+ speciationEvent();
+ /*
+ * stop conditions includes (these are or) 1) extant numbers match those
+ * desired 2) stop time has been reached
+ */
+ while (checkStopConditions()) {
+ double dt = timeToNextSPEvent();
+ // System.out.println(timeToNextSPEvent());
+ double at = timeToNextBioGeoEvent();
+ // System.out.println(at);
+ // if biogeo event is before the speciation event
+ if (dt < at) {
+ currenttime += dt;
+ speciationEvent();
+ }
+ // else if speciation event is before biogeo event
+ else {
+ currenttime += at;
+ biogeoEvent();
+ }
+ if (extantNodes.size() < 1) {
+ failure++;
+ // start
+ curdead = 0;
+ currenttime = 0.0;
+ lineages = new ArrayList<Lineage>();
+ tantlineages = new ArrayList<Lineage>();
+ extantNodes = new ArrayList<Node>();
+ finalT = new Tree();
+ root = new Node();
+ finalT.addExternalNode(root);
+ rt = new Lineage(root, numareas);
+ rt.addArea(0);
+ lineages.add(rt);
+ tantlineages.add(rt);
+ extantNodes.add(root);
+ // set the time at the beginning of the node
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ // set the name of the root node
+ root.setName("root");
+ }
+ if (failure >= 1000) {
+ System.out
+ .println("should probably change the parameters because this has looped 1000 times without a successful time");
+ System.exit(0);
+ }
+ }
+ /*
+ * just post simulation wrap-up
+ */
+ int i, n = extantNodes.size();
+ for (i = 0; i < n; i++) {
+ Node x = extantNodes.get(i);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ }
+ finalT.setRoot(root);
+ finalT.processRoot();
+ for (i = 0; i < finalT.getExternalNodeCount(); i++) {
+ finalT.getExternalNode(i).setName(String.valueOf(i + 1));
+ }
+ /*
+ * print the stuff at the end
+ */
+ TreePrinter tp = new TreePrinter();
+ System.out.println(tp.printNH(finalT) + ";");
+
+ /*
+ * write the output files for area
+ */
+ try {
+ fw = new FileWriter(
+ "/home/smitty/programming/netbeans/NuAReA/test/test.aln");
+ fw.append(tantlineages.size() + " " + numareas + "\n");
+ fwt = new FileWriter(
+ "/home/smitty/programming/netbeans/NuAReA/test/test.tre");// added
+ // true
+ // to
+ // append
+ } catch (IOException ioe) {
+ }
+ for (i = 0; i < tantlineages.size(); i++) {
+ System.out.println(((Lineage) tantlineages.get(i)).getNode()
+ .getName()
+ + " " + ((Lineage) tantlineages.get(i)).getString());
+ try {
+ fw.append(((Lineage) tantlineages.get(i)).getNode().getName() + " " + ((Lineage) tantlineages.get(i)).getString() + "\n");
+ } catch (IOException ioe) {
+ }
+ }
+ System.out.println("-----");
+ // for(i=0;i<lineages.size();i++){
+ // System.out.println(((Lineage)lineages.get(i)).getNode().getString()+"
+ // "+
+ // ((Lineage)lineages.get(i)).getString());
+ // }
+ if (showExtinct == false) {
+ for (i = 0; i < extinctlineages.size(); i++) {
+ System.out.println("dead "
+ + ((Lineage) extinctlineages.get(i)).getNode()
+ .getName());
+ }
+ for (i = 0; i < extinctlineages.size(); i++) {
+ deathLineageTotal(((Lineage) extinctlineages.get(i)).getNode());
+ finalT.processRoot();
+ }
+
+ // pts.report(finalT, 100);
+ finalT.getRoot().setName(((Lineage) lineages.get(0)).getString());
+ /*
+ * print out the number of nodes that change / try{ //fwp = new
+ * FileWriter("/home/smitty/programming/working_copy/bgsim/stuff/test5.run",true);
+ * fwc = new
+ * FileWriter("/home/smitty/programming/working_copy/bgsim/stuff/changes",true);
+ * }catch(IOException ioe){}; ch = 0;
+ * getNumOfNodeChanges(finalT.getRoot()); System.out.println("number
+ * of changed nodes "+ch); try{ //fwp.append("ch =
+ * "+((double)ch/(finalT.getExternalNodeCount()+finalT.getInternalNodeCount()))+"\n");
+ * fwc.append(((double)ch/(finalT.getExternalNodeCount()+finalT.getInternalNodeCount()))+"\n");
+ * //fwp.flush(); //fwp.close(); fwc.flush(); fwc.close();
+ * }catch(IOException ioe){} ///* end nodes that change
+ */
+ // System.out.println(pt.printNH()+":"+((Lineage)lineages.get(0)).getString()+";");
+ System.out.println(tp.printNH(finalT)
+ + ((Lineage) lineages.get(0)).getString() + ";");
+ try {
+ fwt.append(tp.printNH(finalT)
+ + ((Lineage) lineages.get(0)).getString() + ";" + "\n");
+ } catch (IOException ioe) {
+ }
+ } else {// showextinct == false
+ System.out.println(tp.printNH(finalT) + ";");
+ try {
+ fwt.append(tp.printNH(finalT) + ";" + "\n");
+ } catch (IOException ioe) {
+ }
+ }
+ /*
+ * close the file for writing
+ */
+ try {
+ fw.flush();
+ fwt.flush();
+ fw.close();
+ fwt.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ private void getNumOfNodeChanges(Node node) {
+ String nodeR = node.getName();
+ for (int i = 0; i < node.getChildCount(); i++) {
+ if (node.getChild(i).isInternal()) {
+ if (nodeR.compareTo(node.getChild(i).getName()) != 0) {
+ ch++;
+ // System.out.println("--"+nodeR+"
+ // "+node.getChild(i).getString());
+ } else {
+ // System.out.println("++"+nodeR+"
+ // "+node.getChild(i).getString());
+ }
+ } else {
+ for (int j = 0; j < tantlineages.size(); j++) {
+ if (((Lineage) tantlineages.get(j)).getNode().getName()
+ .compareTo(node.getChild(i).getName()) == 0) {
+ if (((Lineage) tantlineages.get(j)).getString()
+ .compareTo(nodeR) != 0) {
+ ch++;
+ // System.out.println("--"+nodeR+"
+ // "+((Lineage)tantlineages.get(j)).getString());
+ } else {
+ // System.out.println("++"+nodeR+"
+ // "+((Lineage)tantlineages.get(j)).getString());
+ }
+ }
+ }
+ }
+ getNumOfNodeChanges(node.getChild(i));
+ }
+ }
+
+ /*
+ * these stop conditions are
+ */
+ private boolean checkStopConditions() {
+ boolean keepgoing = true;
+ if (extantstop > 0 && extantNodes.size() >= extantstop) {
+ currenttime += timeToNextSPEvent();
+
+ keepgoing = false;
+ }
+ if (timestop > 0.0 && currenttime >= timestop) {
+ currenttime = timestop;
+ keepgoing = false;
+ }
+ return keepgoing;
+ }
+
+ private double timeToNextSPEvent() {
+ // return (-Math.log(fRan.fRan(0.1)) /
+ // ((double)(numExtantNodes) * sumrate))*10;
+ return (-Math.log(ran.nextDouble()))
+ / ((double) (extantNodes.size()) * birthrate);
+ }
+
+ private double timeToNextBioGeoEvent() {
+ return (-Math.log(ran.nextDouble()))
+ / ((double) (extantNodes.size()) * sumrate);
+ }
+
+ private boolean eventIsBirth() {
+ return (ran.nextDouble() < birthrate ? true : false);
+ }
+
+ private void speciationEvent() {
+ /*
+ * pick a random lineage
+ */
+ int extant = ran.nextInt(extantNodes.size());
+
+ if (eventIsBirth()) {
+ lineageBirth(extant, 2);// real speciation
+ }
+ }
+
+ private boolean eventIsDispersal() {
+ return (ran.nextDouble() < relativedispersalrate ? true : false);
+ }
+
+ private void biogeoEvent() {
+ /*
+ * pick a random lineage
+ */
+ int extant = ran.nextInt(extantNodes.size());
+ if (eventIsDispersal()) {
+ lineageDispersal(extant);
+ } else {
+ localExtinction(extant);
+ }
+ }
+
+ /*
+ * takes into account only available connections
+ */
+ private void lineageDispersal(int index) {
+ if (((Lineage) tantlineages.get(index)).getCurNumAreas() < numareas) {
+ // get current area//could add population size here
+ int ar = ((Lineage) tantlineages.get(index)).getRandomArea();
+ Area a = areas.get(ar);
+ // test for a relevant to area
+ Connection con = a.getRandomRelaventConnection(currenttime);
+ if (con != null) {
+ int[] ars = ((Lineage) tantlineages.get(index)).getAreas();
+ // test for successful dispersal
+ double rn = ran.nextDouble();
+ if (rn < con.getFunc()) {
+ ars[con.getDest().getIndex()] = 1;
+ // System.out.println("ss "+con.getDest().getIndex());
+ }
+ ((Lineage) tantlineages.get(index)).setAreas(ars);
+ }
+ }
+ }
+
+ /*
+ * should create lineages and place lineages in areas in concordance with
+ * Ree et al. 2005 therefore you either choose sympatric or vicariance
+ */
+ private void lineageBirth(int index, int numofchild) {
+ /*
+ * if there is only one area, then the speciation mode is sympatric both
+ * children inherit the parent area
+ */
+ Node node1 = new Node(extantNodes.get(index));
+ Node node2 = new Node(extantNodes.get(index));
+ extantNodes.get(index).addChild(node1);
+ extantNodes.get(index).addChild(node2);
+ node1.assocObject(BIRTHTIME, new Double(currenttime));
+ node2.assocObject(BIRTHTIME, new Double(currenttime));
+ extantNodes.get(index).setName("in" + curdead);
+ extantNodes.get(index).setName(
+ ((Lineage) tantlineages.get(index)).getString());
+ // System.out.println("in"+curdead);
+ curdead++;
+ int [][][] iters = jade.math.Utils.iter_splitranges(((Lineage) tantlineages.get(index)).getAreas());
+ int x = ran.nextInt(iters.length);
+ deathLineage(index);
+ tantlineages.remove(index);
+ finalT.addExternalNode(node1);
+ finalT.addExternalNode(node2);
+ extantNodes.add(node1);
+ extantNodes.add(node2);
+ Lineage rt = new Lineage(node1, numareas);
+ rt.setAreas(iters[x][0]);
+ lineages.add(rt);
+ tantlineages.add(rt);
+ rt = new Lineage(node2, numareas);
+ rt.setAreas(iters[x][1]);
+ lineages.add(rt);
+ tantlineages.add(rt);
+ /*
+ if (((Lineage) tantlineages.get(index)).getCurNumAreas() == 1) {
+ deathLineage(index);
+ int newA1 = ((Lineage) tantlineages.get(index)).getRandomArea();
+ // extinctlineages.add(tantlineages.get(index));
+ tantlineages.remove(index);
+ finalT.addExternalNode(node1);
+ finalT.addExternalNode(node2);
+ extantNodes.add(node1);
+ extantNodes.add(node2);
+ Lineage rt = new Lineage(node1, numareas);
+ rt.addArea(newA1);// one inherits the old area
+ lineages.add(rt);
+ tantlineages.add(rt);
+ rt = new Lineage(node2, numareas);
+ rt.addArea(newA1);// one randomly disperses
+ lineages.add(rt);
+ tantlineages.add(rt);
+ } else {// decide between sympatric or vicariance
+ double vic = ran.nextDouble();
+ int newA2 = ((Lineage) tantlineages.get(index)).getRandomArea();
+ int[] newA1 = ((Lineage) tantlineages.get(index)).getAreas();
+ int[] cnewA1 = (int[]) newA1.clone();
+ deathLineage(index);
+ // extinctlineages.add(tantlineages.get(index));
+ tantlineages.remove(index);
+ // does not correctly seperate the lineages for vicariance
+ finalT.addExternalNode(node1);
+ finalT.addExternalNode(node2);
+ extantNodes.add(node1);
+ extantNodes.add(node2);
+ Lineage rt = new Lineage(node1, numareas);
+ rt.setAreas(cnewA1);// one inherits the old areas
+ lineages.add(rt);
+ tantlineages.add(rt);
+ if (vic < 0.5) {
+ rt.removeArea(newA2);
+ // System.out.println(newA2);
+ }
+ Lineage rt2 = new Lineage(node2, numareas);
+ rt2.addArea(newA2);// one takes a random one
+ lineages.add(rt2);
+ tantlineages.add(rt2);
+ }*/
+ }
+
+ private void localExtinction(int index) {
+ if (((Lineage) tantlineages.get(index)).getCurNumAreas() > 1) {
+ ((Lineage) tantlineages.get(index)).randomRemoveArea();
+ if (((Lineage) tantlineages.get(index)).getCurNumAreas() == 0) {
+ deathLineage(index);
+ extinctlineages.add(tantlineages.get(index));
+ tantlineages.remove(index);
+ }
+ } else {
+ deathLineage(index);
+ extinctlineages.add(tantlineages.get(index));
+ tantlineages.remove(index);
+ }
+ // System.out.println("x");
+ }
+
+ private void deathLineage(int index) {
+ Node x = extantNodes.get(index);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ extantNodes.remove(index);
+ }
+
+ private void deathLineageTotal(Node innode) {
+ Node parent = null;
+ if (innode.isTheRoot()) {
+ parent = innode;
+ } else {
+ parent = innode.getParent();
+ }
+
+ /*
+ * fix for if the parent is the root
+ */
+ Node node2 = null;
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ if (parent.getChild(i) != innode)
+ node2 = parent.getChild(i);
+ }
+ if (parent.isTheRoot()) {
+ /*
+ * assumes there are at least three taxa in the tree
+ */
+ finalT.setRoot(node2);
+ } else {
+ Node node3 = parent.getParent();
+ /*
+ * error here
+ */
+ node3.removeChild(parent);
+ node3.addChild(node2);
+ node2.setParent(node3);
+ }
+ }
+
+ public static void main(String[] args) {
+ Area ar1 = new Area(0);// EA
+ Area ar2 = new Area(1);// WNA
+ Area ar3 = new Area(2);// ENA
+ Area ar4 = new Area(3);// EU
+ // 1
+ ar1.addConnection(ar2, 1000, 0, 1);
+ ar1.addConnection(ar3, 1000, 0, 1);
+ ar1.addConnection(ar4, 1000, 0, 1);
+ // 2
+ ar2.addConnection(ar1, 1000, 0, 1);
+ ar2.addConnection(ar3, 1000, 0, 1);
+ ar2.addConnection(ar4, 1000, 0, 1);
+ // 3
+ ar3.addConnection(ar2, 1000, 0, 1);
+ ar3.addConnection(ar1, 1000, 0, 1);
+ ar3.addConnection(ar4, 1000, 0, 1);
+ // 4
+ ar4.addConnection(ar2, 1000, 0, 1);
+ ar4.addConnection(ar3, 1000, 0, 1);
+ ar4.addConnection(ar1, 1000, 0, 1);
+ BioGeoTree bgt = new BioGeoTree();
+ ArrayList<Area> ar = new ArrayList<Area>();
+ ar.add(ar1);
+ ar.add(ar2);
+ ar.add(ar3);
+ ar.add(ar4);
+ bgt.setModel(ar);
+ bgt.start();
+ }
+
+}
diff --git a/src/jade/simulate/BioGeoTree2.java b/src/jade/simulate/BioGeoTree2.java
new file mode 100644
index 0000000..7d2ec5a
--- /dev/null
+++ b/src/jade/simulate/BioGeoTree2.java
@@ -0,0 +1,614 @@
+package jade.simulate;
+
+import jade.tree.*;
+
+import java.util.*;
+import java.io.*;
+
+public class BioGeoTree2 {
+ private ArrayList<Node> extantNodes;
+ private ArrayList<Node> allNodes;
+ private static String BIRTHTIME = "birthtime";
+ private static String DEATHTIME = "deathtime";
+ private static String SETAREAS = "set_areas";
+ private static String CURAREAS = "cur_areas";
+ private int failures;
+ private int maxfail = 10000;
+ private double relativedispersalrate ;
+ private Tree finalT;
+ private Random ran;
+ public ArrayList<Area> areas;
+ private double sumrate;
+ private double currenttime;
+ private int numofchanges;
+ private ArrayList<Node> numdisp;
+ private ArrayList<Node> numext;
+ private ArrayList<Node> numapext;
+ private ArrayList<Node> numapdisp;
+ private boolean printnumCH = true;
+ private Node dispnode ;
+ public double apdisp;
+ public double apext;
+ public double redisp;
+ public double reext;
+
+ public double vag;// birth rate in an area
+ public double birthrate;// speciation rate
+ public double deathrate;// in an area, which translates to
+ public double extantstop = 0;
+ public double timestop = 0;
+ public boolean DEBUG = false;
+
+ /*
+ * added for constrained extinction
+ * the idea is that in addition to the global extinction rate, there is a localized extinction rate for different areas
+ * make sure the length is the right length (size of the areas)
+ */
+ public boolean areaspecificextinction = false;
+ public void setLocalExtinction(ArrayList<Double> arearates){
+ areaspecificextinction = true;
+ for(int i=0;i<arearates.size();i++){
+ ((Area)this.areas.get(i)).setLocalExtinctionRate(arearates.get(i));
+ }
+ }
+
+ /*
+ * added for speciation linked to dispersal, if a species disperses then it speciates
+ */
+ public boolean speciationlinked = false;
+ public void setDispersalLinkedToSpeciation(boolean set){
+ this.speciationlinked = true;
+ }
+
+ /*
+ * added for constrained dispersal
+ */
+
+ /*
+ * used to count the number of changes
+ */
+ private int ch;
+
+ public BioGeoTree2(){
+ failures = 0;
+ }
+
+ public BioGeoTree2(ArrayList<Area> areas){
+ this.areas = areas;
+ }
+
+ public Tree makeTree(boolean showDead){
+ this.setup_parameters();
+ finalT = new Tree();
+ Node root = new Node();
+ finalT.setRoot(root);
+ int [] ar = new int [areas.size()];
+ for(int i=0;i<ar.length;i++){ar[i]=0;}
+ ar[ran.nextInt(ar.length)] = 1;//for one area
+ if(DEBUG){
+ for(int i=0;i<ar.length;i++){System.out.print(ar[i]);}
+ System.out.println();
+ }
+ root.assocObject(SETAREAS, (int [])ar.clone());
+ root.setName(get_area_string((int [])root.getObject(SETAREAS)));
+ root.assocObject(CURAREAS, ar);//to be changed
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ extantNodes.add(root);
+ //nodeBirth(root);
+ while (checkStopConditions()) {
+ try{
+ double dt = timeToNextSPEvent();
+ double at = timeToNextBioGeoEvent();
+ if (dt < at) {
+ if(DEBUG)
+ System.out.println("spevent");
+ currenttime += dt;
+ speciationEvent();
+ }
+ // else if speciation event is before biogeo event
+ else {
+ if(DEBUG)
+ System.out.println("biogeoevent");
+ currenttime += at;
+ biogeoEvent();
+ }
+ for(int i=0;i<extantNodes.size();i++){
+ if(jade.math.Utils.sum((int [])(extantNodes.get(i).getObject(CURAREAS)))<1){
+ killNode(extantNodes.get(i));
+ //System.out.println("a");
+ }
+ }
+ if (extantNodes.size() < 1) {
+ failures++;
+ this.setup_parameters();
+ finalT = new Tree();
+ root = new Node();
+ finalT.setRoot(root);
+ ar = new int [areas.size()];
+ for(int i=0;i<ar.length;i++){ar[i]=0;}
+ ar[ran.nextInt(ar.length)] = 1;//for one area
+ root.assocObject(SETAREAS, ar.clone());
+ root.setName(get_area_string((int [])root.getObject(SETAREAS)));
+ root.assocObject(CURAREAS, ar);//to be changed
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ extantNodes = new ArrayList<Node>();
+ allNodes = new ArrayList<Node>();
+ extantNodes.add(root);
+ allNodes.add(root);
+ //nodeBirth(root);
+ }
+ }catch(java.lang.IllegalArgumentException iae){
+ failures++;
+ if(failures > maxfail){
+ System.out.println("change parameters");
+ System.exit(0);
+ }
+ this.setup_parameters();
+ finalT = new Tree();
+ root = new Node();
+ finalT.setRoot(root);
+ ar = new int [areas.size()];
+ for(int i=0;i<ar.length;i++){ar[i]=0;}
+ ar[ran.nextInt(ar.length)] = 1;//for one area
+ root.assocObject(SETAREAS, ar.clone());
+ root.setName(get_area_string((int [])root.getObject(SETAREAS)));
+ root.assocObject(CURAREAS, ar);//to be changed
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ extantNodes = new ArrayList<Node>();
+ allNodes = new ArrayList<Node>();
+ extantNodes.add(root);
+ allNodes.add(root);
+ }
+ }
+ finalT.processRoot();
+ /*
+ * set internal branch lengths
+ */
+ for(int i=0;i<finalT.getExternalNodeCount();i++){
+ if(finalT.getExternalNode(i).getObject(DEATHTIME)==null){
+ killNode(finalT.getExternalNode(i));
+ }
+ finalT.getExternalNode(i).setName(get_area_string((int [])finalT.getExternalNode(i).getObject(CURAREAS)));
+ }double ortotallength = 0.0;
+ if(printnumCH == true){
+ double totallength = 0.0;
+ for(int i = 0;i < finalT.getExternalNodeCount(); i++){
+ totallength+= finalT.getExternalNode(i).getBL();
+ }for(int i = 0;i < finalT.getInternalNodeCount(); i++){
+ totallength+= finalT.getInternalNode(i).getBL();
+ }
+ redisp = (numdisp.size()/totallength);
+ reext = (numext.size())/(totallength);
+ //System.out.print((numdisp.size()/(totallength)) +"\t"+((numext.size()+finalT.getInternalNodeCount())/(totallength)+"\t")
+ // +"\t");
+ ortotallength = totallength;
+ }
+ if(showDead == false){
+ deleteDeadNodes();
+ }
+ if(printnumCH == true){
+ double totallength = 0.0;
+ for(int i = 0;i < finalT.getExternalNodeCount(); i++){
+ totallength+= finalT.getExternalNode(i).getBL();
+ for(int j=0;j<numdisp.size();j++){
+ if(numdisp.get(j) == finalT.getExternalNode(i)){
+ numapdisp.add(numdisp.get(j));
+ }
+ }
+ for(int j=0;j<numext.size();j++){
+ if(numext.get(j) == finalT.getExternalNode(i)){
+ numapext.add(numext.get(j));
+ }
+ }
+ }for(int i = 0;i < finalT.getInternalNodeCount(); i++){
+ totallength+= finalT.getInternalNode(i).getBL();
+ for(int j=0;j<numdisp.size();j++){
+ if(numdisp.get(j) == finalT.getInternalNode(i)){
+ numapdisp.add(numdisp.get(j));
+ }
+ }
+ for(int j=0;j<numext.size();j++){
+ if(numext.get(j) == finalT.getInternalNode(i)){
+ numapext.add(numext.get(j));
+ }
+ }
+ }
+ apdisp = (numapdisp.size()/totallength);
+ apext = ((numapext.size())/totallength);//apext = ((numext.size())/totallength);
+ //System.out.print((numapdisp.size()/ortotallength)+"\t"+(((numext.size()/2)+numapext.size())/totallength)+"\n");
+ }
+ return finalT;
+ }
+
+ private boolean checkStopConditions() {
+ boolean keepgoing = true;
+ if (extantstop > 0 && extantNodes.size() >= extantstop) {
+ currenttime += timeToNextSPEvent();
+ keepgoing = false;
+ }
+ if (timestop > 0.0 && currenttime >= timestop) {
+ currenttime = timestop;
+ keepgoing = false;
+ }
+ return keepgoing;
+ }
+
+ private void setup_parameters(){
+ numofchanges = 0;
+ numdisp = new ArrayList<Node>();
+ numext = new ArrayList<Node>();
+ numapext = new ArrayList<Node>();
+ numapdisp= new ArrayList<Node>();
+ ran = new Random();
+ currenttime = 0.0;
+ relativedispersalrate = vag / (vag+deathrate);
+ sumrate = vag + deathrate;
+ extantNodes = new ArrayList<Node>();
+ allNodes = new ArrayList<Node>();
+ }
+
+ private void speciationEvent(){
+ /*
+ * pick a random lineage
+ */
+ Node extant = extantNodes.get(ran.nextInt(extantNodes.size()));
+
+ if (eventIsBirth()) {
+ nodeBirth(extant);// real speciation
+ }
+ }
+
+ private void biogeoEvent() {
+ /*
+ * pick a random lineage
+ */
+ Node extant = extantNodes.get(ran.nextInt(extantNodes.size()));
+ if (eventIsDispersal()) {
+ nodeDispersal(extant);
+ } else {
+ nodeExtinction(extant);
+ }
+ }
+
+ private boolean eventIsDispersal() {
+ return (ran.nextDouble() < relativedispersalrate ? true : false);
+ }
+
+ private boolean eventIsBirth() {
+ return (ran.nextDouble() < birthrate ? true : false);
+ }
+
+ private void nodeBirth(Node node){
+ Node left = new Node(node);
+ Node right = new Node(node);
+ node.addChild(left);
+ node.addChild(right);
+ left.assocObject(BIRTHTIME, new Double(currenttime));
+ right.assocObject(BIRTHTIME, new Double(currenttime));
+ killNode(node);
+ extantNodes.add(left);
+ extantNodes.add(right);
+ allNodes.add(left);
+ allNodes.add(right);
+ node.setName(get_area_string((int [])node.getObject(CURAREAS)));
+ //if(jade.math.Utils.sum((int [])node.getObject(CURAREAS))>1){
+ // numapext.add(node);
+ //}
+ int [][][] iter = jade.math.Utils.iter_splitranges((int [])node.getObject(CURAREAS));
+ int x = ran.nextInt(iter.length);
+ left.assocObject(SETAREAS, (int [])iter[x][0].clone());
+ right.assocObject(SETAREAS, (int [])iter[x][1].clone());
+ left.assocObject(CURAREAS, (int [])iter[x][0].clone());//to be changed
+ right.assocObject(CURAREAS, (int[])iter[x][1].clone());//to be changed
+ left.setName(get_area_string((int [])left.getObject(SETAREAS)));
+ right.setName(get_area_string((int [])right.getObject(SETAREAS)));
+ }
+
+ /*
+ * node birth into specific area for speciation linked with dispersal
+ */
+ private void nodeBirth(Node node, int sparea){
+ Node left = new Node(node);
+ Node right = new Node(node);
+ node.addChild(left);
+ node.addChild(right);
+ left.assocObject(BIRTHTIME, new Double(currenttime));
+ right.assocObject(BIRTHTIME, new Double(currenttime));
+ killNode(node);
+ extantNodes.add(left);
+ extantNodes.add(right);
+ allNodes.add(left);
+ allNodes.add(right);
+ node.setName(get_area_string((int [])node.getObject(CURAREAS)));
+
+ int [] x = new int [ this.areas.size()];
+ for(int i=0;i<x.length;i++){
+ x[i] = 0;
+ if(i == sparea)
+ x[i] = 1;
+ }
+ left.assocObject(SETAREAS, ((int [])node.getObject(CURAREAS)).clone());
+ right.assocObject(SETAREAS, (int [])x.clone());
+ left.assocObject(CURAREAS, ((int [])node.getObject(CURAREAS)).clone());//to be changed
+ right.assocObject(CURAREAS, (int[])x.clone());//to be changed
+ left.setName(get_area_string((int [])left.getObject(SETAREAS)));
+ right.setName(get_area_string((int [])right.getObject(SETAREAS)));
+ }
+
+ private void nodeExtinction(Node node){
+ int [] ar = (int [])node.getObject(CURAREAS);
+ int x = 0;
+ if(areaspecificextinction == false){
+ x = ran.nextInt(jade.math.Utils.sum(ar));
+ }else{
+ double tx = ran.nextDouble();
+ double start = 0;
+ double end = 0;
+ for(int i=0;i<areas.size();i++){
+ end = start + areas.get(i).getLocalExtinctionRate();
+ if(tx > start && tx <= end){
+ x = i;
+ break;
+ }
+ start = end;
+ }
+ }
+ int cur = 0;
+ for(int i=0;i<ar.length;i++){
+ if(ar[i] == 1 && x == cur){
+ ar[i] = 0;
+ numext.add(node);
+ break;
+ }
+ if(ar[i]==1){
+ cur ++;
+ }
+ }
+ if(jade.math.Utils.sum(ar)<1){
+ killNode(node);
+ }
+ node.assocObject(CURAREAS, ar);
+ }
+
+ private void nodeDispersal(Node node){
+ int [] ar = (int [])node.getObject(CURAREAS);
+ /*
+ * new way allows for directionality
+ */
+ if(jade.math.Utils.sum(ar)!=areas.size()){
+ /*
+ * pick source area
+ */
+ int x = ran.nextInt((jade.math.Utils.sum(ar)));
+ int cur = 0;
+ Area sa = null;
+ int sarea = 0;
+ for(int i=0;i<ar.length;i++){
+ if(ar[i] == 1){
+ if(x == cur){
+ sa = areas.get(i);
+ sarea = i;
+ }
+ cur++;
+ }
+ }
+ /*
+ * process other areas
+ *//*
+ double total = 0 ;
+ for(int i=0;i<ar.length;i++){
+ if(areas.get(i)!=sa && ar[i] == 0){
+ total += areas.get(i).getLocalDispersalRate(sa);
+ }
+ }
+ ArrayList<Double> probs = new ArrayList<Double>();
+ for(int i=0;i<ar.length;i++){
+ if(areas.get(i)!=sa && ar[i] == 0){
+ probs.add(areas.get(i).getLocalDispersalRate(sa)/total);
+ }else{
+ probs.add(666.0);
+ }
+ }*/
+ /*
+ * attempt dispersal
+ */
+ double rt = ran.nextDouble();
+ double start = 0;
+ for(int i=0;i<ar.length;i++){
+ if(areas.get(i)!=sa && ar [i] == 0){
+ double end = start + sa.getLocalDispersalRate(areas.get(i),currenttime);
+ if(rt > start && rt <= end && sa.getLocalDispersalRate(areas.get(i),currenttime)!= 0){
+ if(this.speciationlinked == false){
+ ar[i] = 1;
+ sa.addSuccDisp(areas.get(i));
+ //if(sarea == 0 && i ==2 && currenttime < 10)
+ // System.out.println("error");
+ }
+ numdisp.add(node);
+ if(this.speciationlinked == true){
+ nodeBirth(node,i);// real speciation
+ }
+ break;
+ }
+ start = end;
+ }
+ }
+ if(this.speciationlinked == false)
+ node.assocObject(CURAREAS, ar);
+ }
+ /*
+ * old way
+ */
+ /*if(jade.math.Utils.sum(ar)!=areas.size()){
+ int x = ran.nextInt(areas.size()-(jade.math.Utils.sum(ar)));
+ int cur = 0;
+ for(int i=0;i<ar.length;i++){
+ if(ar[i] == 0 && x == cur){
+ if(this.speciationlinked == false)
+ ar[i] = 1;
+ numdisp.add(node);
+ if(this.speciationlinked == true){
+ nodeBirth(node,i);// real speciation
+ }
+ break;
+ }
+ if(ar[i]==0){
+ cur ++;
+ }
+ }
+ if(this.speciationlinked == false)
+ node.assocObject(CURAREAS, ar);
+
+ }*/
+ }
+
+ private void killNode(Node node){
+ node.assocObject(DEATHTIME, new Double(currenttime));
+ double bl = (Double)node.getObject(DEATHTIME) - (Double)node.getObject(BIRTHTIME);
+ node.setBL(bl);
+ extantNodes.remove(node);
+ }
+
+ private double timeToNextSPEvent() {
+ // return (-Math.log(fRan.fRan(0.1)) /
+ // ((double)(numExtantNodes) * sumrate))*10;
+ return (-Math.log(ran.nextDouble()))
+ / ((double) (extantNodes.size()) * birthrate);
+ }
+
+ private double timeToNextBioGeoEvent() {
+ return (-Math.log(ran.nextDouble()))
+ / ((double) (extantNodes.size()) * sumrate);
+ }
+
+ private String get_area_string(int []ar){
+ String ret = "";
+ for(int i=0;i<ar.length;i++){
+ ret = ret + String.valueOf(ar[i]);
+ }
+ return ret;
+ }
+
+ private void deleteDeadNodes(){
+ ArrayList<Node> kill = new ArrayList<Node>();
+ TreeUtils.setDistanceToTip(finalT);
+ TreeUtils.setDistanceFromTip(finalT);
+ for(int i=0;i<finalT.getExternalNodeCount();i++){
+ //System.out.println(finalT.getExternalNode(i).getDistanceFromTip()+" "+
+ // finalT.getRoot().getDistanceToTip());
+ if(finalT.getExternalNode(i).getDistanceFromTip() !=
+ finalT.getRoot().getDistanceToTip()
+ ){
+ kill.add(finalT.getExternalNode(i));
+ }
+ }
+ //System.out.println(kill.size());
+ for(int i=0;i<kill.size();i++){
+ deleteANode(kill.get(i));
+ }
+
+ }
+
+ private void deleteANode(Node node){
+ Node parent = node.getParent();
+ if(parent != finalT.getRoot()){
+ Node child = null;
+ for(int i=0;i<parent.getChildCount();i++){
+ if(parent.getChild(i)!= node)
+ child = parent.getChild(i);
+ }
+ Node pparent = parent.getParent();
+ parent.removeChild(node);
+ parent.removeChild(child);
+ pparent.removeChild(parent);
+ pparent.addChild(child);
+ child.setParent(pparent);
+ child.setBL(child.getBL()+parent.getBL());
+ finalT.processRoot();
+ }else{
+ Node child = null;
+ for(int i=0;i<parent.getChildCount();i++){
+ if(parent.getChild(i)!= node)
+ child = parent.getChild(i);
+ }
+ parent.removeChild(node);
+ child.setParent(null);
+ finalT.setRoot(child);
+ finalT.processRoot();
+ }
+ }
+
+ private void getNumOfNodeChanges(Node node) {
+ String nodeR = node.getName();
+ for (int i = 0; i < node.getChildCount(); i++) {
+ if (node.getChild(i).isInternal()) {
+ if (nodeR.compareTo(node.getChild(i).getName()) != 0) {
+ ch++;
+ // System.out.println("--"+nodeR+"
+ // "+node.getChild(i).getString());
+ } else {
+ // System.out.println("++"+nodeR+"
+ // "+node.getChild(i).getString());
+ }
+ } else {
+ for (int j = 0; j < extantNodes.size(); j++) {
+ if (( extantNodes.get(j)).getName()
+ .compareTo(node.getChild(i).getName()) == 0) {
+ if (( extantNodes.get(j)).getName()
+ .compareTo(nodeR) != 0) {
+ ch++;
+ // System.out.println("--"+nodeR+"
+ // "+((Lineage)tantlineages.get(j)).getString());
+ } else {
+ // System.out.println("++"+nodeR+"
+ // "+((Lineage)tantlineages.get(j)).getString());
+ }
+ }
+ }
+ }
+ getNumOfNodeChanges(node.getChild(i));
+ }
+ }
+
+ /*
+ * main
+ */
+ public static void main(String [] args){
+ ArrayList<jade.simulate.Area> ar = new ArrayList<jade.simulate.Area>();
+ jade.simulate.Area ar1 = new jade.simulate.Area(0);// EA
+ jade.simulate.Area ar2 = new jade.simulate.Area(1);// WNA
+ jade.simulate.Area ar3 = new jade.simulate.Area(2);// ENA
+ //jade.simulate.Area ar4 = new jade.simulate.Area(3);// EU
+ // 1
+ ar1.addConnection(ar2, 1000, 0, 1);
+ ar1.addConnection(ar3, 1000, 0, 1);
+ //ar1.addConnection(ar4, 1000, 0, 1);
+ // 2
+ ar2.addConnection(ar1, 1000, 0, 1);
+ ar2.addConnection(ar3, 1000, 0, 1);
+ //ar2.addConnection(ar4, 1000, 0, 1);
+ // 3
+ ar3.addConnection(ar2, 1000, 0, 1);
+ ar3.addConnection(ar1, 1000, 0, 1);
+ //ar3.addConnection(ar4, 1000, 0, 1);
+ // 4
+ //ar4.addConnection(ar2, 1000, 0, 1);
+ //ar4.addConnection(ar3, 1000, 0, 1);
+ //ar4.addConnection(ar1, 1000, 0, 1);
+ ar.add(ar1);
+ ar.add(ar2);
+ ar.add(ar3);
+ //ar.add(ar4);
+ BioGeoTree2 bgt= new BioGeoTree2(ar);
+ bgt.birthrate = 0.4;
+ bgt.extantstop = 100;
+ bgt.vag = 0.02;
+ bgt.deathrate = 0.02;
+ Tree tree = bgt.makeTree(true);
+ for(int j=0;j<tree.getExternalNodeCount();j++){
+ System.out.print((j+1)+"\t"+tree.getExternalNode(j).getName()+"\n");
+ tree.getExternalNode(j).setName(String.valueOf(j+1));
+ }
+ System.out.println(tree.getRoot().getNewick(true)+"\n");
+ }
+}
diff --git a/src/jade/simulate/BirthDeathTree.java b/src/jade/simulate/BirthDeathTree.java
new file mode 100644
index 0000000..70b9beb
--- /dev/null
+++ b/src/jade/simulate/BirthDeathTree.java
@@ -0,0 +1,157 @@
+package jade.simulate;
+
+import jade.tree.*;
+import java.util.*;
+
+public class BirthDeathTree extends Tree {
+ private double birthrate = 0.4;
+
+ private double relativebirthrate = 1.0;
+
+ private double deathrate = 0.1;
+
+ private double extantstop = 40;
+
+ private double timestop = 0;
+
+ private double sumrate = 1.0;
+
+ private double currenttime = 0.0;
+
+ private Tree finalT;
+
+ private Random ran;
+
+ private boolean purebirth = false;
+
+ private ArrayList<Node> extantNodes;
+
+ private int numExtantNodes;
+
+ private static String BIRTHTIME = "birthtime";
+
+ private static String DEATHTIME = "deathtime";
+
+ public BirthDeathTree() {
+ ran = new Random();
+ numExtantNodes = 0;
+ extantNodes = new ArrayList<Node>();
+ start();
+ }
+
+ public void start() {
+ // setup parameters
+ if (purebirth == false) {
+ sumrate = birthrate + deathrate;
+ relativebirthrate = birthrate / sumrate;
+ } else {
+ sumrate = birthrate;
+ relativebirthrate = 1.0;
+ }
+ // start
+ finalT = new Tree();
+ Node root = new Node();
+ finalT.addExternalNode(root);
+ numExtantNodes++;
+ extantNodes.add(root);
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ root.setName("root");
+ while (checkStopConditions()) {
+ double dt = timeToNextEvent();
+ currenttime += dt;
+
+ event();
+
+ if (numExtantNodes < 1) {
+ System.out.println("no extant nodes");
+ System.exit(0);
+ }
+ }
+
+ int i, n = numExtantNodes;
+ for (i = 0; i < n; i++) {
+ Node x = extantNodes.get(i);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ }
+ finalT.setRoot(root);
+ finalT.processRoot();
+ for (i = 0; i < finalT.getExternalNodeCount(); i++) {
+ finalT.getExternalNode(i).setName("taxon_" + String.valueOf(i + 1));
+ }
+ TreePrinter tp = new TreePrinter();
+ //PrintWriter pw = new PrintWriter(System.out);
+ //tp.reportASCII(finalT, pw);
+ //pw.flush();
+ System.out.println(tp.printNH(finalT) + ";");
+ }
+
+ public void event() {
+ int extant = ran.nextInt(numExtantNodes);
+
+ if (purebirth || eventIsBirth())
+ lineageBirth(extant, 2);
+ else
+ lineageDeath(extant);
+ }
+
+ public void lineageBirth(int index, int numofchild) {
+ Node node1 = new Node(extantNodes.get(index));
+ Node node2 = new Node(extantNodes.get(index));
+ extantNodes.get(index).addChild(node1);
+ extantNodes.get(index).addChild(node2);
+ node1.assocObject(BIRTHTIME, new Double(currenttime));
+ node2.assocObject(BIRTHTIME, new Double(currenttime));
+ lineageDeath(index);
+ finalT.addExternalNode(node1);
+ finalT.addExternalNode(node2);
+ extantNodes.add(node1);
+ extantNodes.add(node2);
+ numExtantNodes++;
+ numExtantNodes++;
+ }
+
+ public void lineageDeath(int index) {
+ Node x = extantNodes.get(index);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ extantNodes.remove(index);
+ numExtantNodes--;
+ }
+
+ public boolean checkStopConditions() {
+ boolean keepgoing = true;
+ if (extantstop > 0 && numExtantNodes >= extantstop) {
+ currenttime += timeToNextEvent();
+ keepgoing = false;
+ }
+ if (timestop > 0.0 && currenttime >= timestop) {
+ currenttime = timestop;
+ keepgoing = false;
+ }
+ return keepgoing;
+ }
+
+ public double timeToNextEvent() {
+ // return -log((double)MERandom::fRan()) /
+ // (double(mPhylogeny->getNumExtantLineages()) * mSumRate);
+ // return -Math.log(ran.nextDouble()) /
+ // ((double)(finalT.getNumberOfExtantNodes()) * sumrate);
+ // return -Math.log(fRan.fRan(1.0)) /
+ // ((double)(finalT.getNumberOfExtantNodes()) * sumrate);
+ return (-Math.log(ran.nextDouble()) * 1.0)
+ / ((double) (numExtantNodes) * sumrate);
+ }
+
+ public boolean eventIsBirth() {
+ return (ran.nextDouble() < relativebirthrate ? true : false);
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 1; i++) {
+ new BirthDeathTree();
+ }
+ }
+}
diff --git a/src/jade/simulate/Connection.java b/src/jade/simulate/Connection.java
new file mode 100644
index 0000000..ecc5d12
--- /dev/null
+++ b/src/jade/simulate/Connection.java
@@ -0,0 +1,18 @@
+package jade.simulate;
+
+public class Connection {
+ public Connection(Area dest, double start, double end, double func){
+ this.dest = dest;
+ this.start = start;
+ this.end = end;
+ this.func = func;
+ }
+ public Area getDest(){return dest;}
+ public double getStart(){return start;}
+ public double getEnd(){return end;}
+ public double getFunc(){return func;}
+ private Area dest;
+ private double start;
+ private double end;
+ private double func;
+}
diff --git a/src/jade/simulate/ConstantEpisode.java b/src/jade/simulate/ConstantEpisode.java
new file mode 100644
index 0000000..62a1213
--- /dev/null
+++ b/src/jade/simulate/ConstantEpisode.java
@@ -0,0 +1,12 @@
+package jade.simulate;
+
+public class ConstantEpisode{
+ public int extantstop;
+ public int byextantstop;
+ public double timestop;
+ public double bytimestop;
+ public double multipletimestop;
+ public boolean stochastic;
+ public double birthdeathrate;
+ public String stoptype;
+}
diff --git a/src/jade/simulate/EpisodicTree.java b/src/jade/simulate/EpisodicTree.java
new file mode 100644
index 0000000..e55ad38
--- /dev/null
+++ b/src/jade/simulate/EpisodicTree.java
@@ -0,0 +1,368 @@
+package jade.simulate;
+
+import jade.math.fRan;
+
+import java.util.*;
+
+import jade.tree.*;
+
+public class EpisodicTree {
+ private static String BIRTHTIME = "birthtime";
+
+ private static String DEATHTIME = "deathtime";
+
+ private ArrayList<Node> extantNodes;
+
+ private int numepisodes = 0;
+
+ private ArrayList<String> episodes;
+
+ private int numgrowth = 0;
+
+ private ArrayList<GrowthEpisode> growthepisodes;
+
+ private int numconstant = 0;
+
+ private ArrayList<ConstantEpisode> constantepisodes;
+
+ private int numextinction = 0;
+
+ private ArrayList<ExtinctionEpisode> extinctionepisodes;
+
+ private int lastextantlineages = 0;
+
+ private double lastepisodetime = 0.0;
+
+ private double lastcurrenttime = 0.0;
+
+ private double birthrate = 1.0;
+
+ private double relativebirthrate = 1.0;
+
+ private double deathrate = 0.2;
+
+ private double sumrate = 1.0;
+
+ private double currenttime = 0.0;
+
+ private Tree finalT;
+
+ private Random ran;
+
+ public EpisodicTree() {
+ extantNodes = new ArrayList<Node>();
+ ran = new Random();
+ // addextinction fraction=0.1
+ // addgrowth birth=0.2 death=0.1 extant=1000
+ growthepisodes = new ArrayList<GrowthEpisode>();
+ extinctionepisodes = new ArrayList<ExtinctionEpisode>();
+ episodes = new ArrayList<String>();
+ // one growth
+ GrowthEpisode ge = new GrowthEpisode();
+ ge.birthrate = 1;
+ ge.deathrate = 0;
+ ge.stoptype = "extantstop";
+ ge.extantstop = 20;
+ numgrowth++;
+ numepisodes++;
+ growthepisodes.add(ge);
+ episodes.add("growth");
+ // two extinct
+ ExtinctionEpisode ee = new ExtinctionEpisode();
+ ee.fraction = 0.5;
+ ee.type = "fraction";
+ ee.number = 0;
+ extinctionepisodes.add(ee);
+ numextinction++;
+ numepisodes++;
+ episodes.add("extinction");
+ // three growth
+ GrowthEpisode ge2 = new GrowthEpisode();
+ ge2.birthrate = 1;
+ ge2.deathrate = 0;
+ ge2.stoptype = "extantstop";
+ ge2.extantstop = 40;
+ numgrowth++;
+ numepisodes++;
+ growthepisodes.add(ge2);
+ episodes.add("growth");
+ start();
+ }
+
+ public void start() {
+ lastextantlineages = 0;
+
+ lastepisodetime = 0.0;
+ lastcurrenttime = 0.0;
+
+ // start
+ finalT = new Tree();
+ Node root = new Node();
+ finalT.addExternalNode(root);
+ extantNodes.add(root);
+ root.assocObject(BIRTHTIME, new Double(currenttime));
+ root.setName("root");
+
+ int cge = 0;
+ int cce = 0;
+ int cee = 0;
+ for (int i = 0; i < numepisodes; i++) {
+ if (episodes.get(i) == "growth") {
+ simulateGrowthEpisode(i, (GrowthEpisode) growthepisodes
+ .get(cge));
+ lastepisodetime = currenttime - lastcurrenttime;
+ cge++;
+ } else if (episodes.get(i) == "constant") {
+ simulateConstantEpisode(i, (ConstantEpisode) constantepisodes
+ .get(cce));
+ cce++;
+ lastepisodetime = currenttime - lastcurrenttime;
+ } else if (episodes.get(i) == "extinction") {
+ simulateExtinctionEpisode(i,
+ (ExtinctionEpisode) extinctionepisodes.get(cee));
+ cee++;
+ // don't set mLastEpisodeTime because an extinction is
+ // instantaneous.
+ }
+ lastextantlineages = extantNodes.size();
+ lastcurrenttime = currenttime;
+ }
+
+ int n = extantNodes.size();
+ for (int i = 0; i < n; i++) {
+ Node x = extantNodes.get(i);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ }
+ finalT.setRoot(root);
+ finalT.processRoot();
+ for (int i = 0; i < finalT.getExternalNodeCount(); i++) {
+ finalT.getExternalNode(i).setName(String.valueOf(i + 1));
+ }
+ TreePrinter tp = new TreePrinter();
+ //PrintWriter pw = new PrintWriter(System.out);
+ //tp.reportASCII(finalT, pw);
+ //pw.flush();
+ System.out.println(tp.printNH(finalT) + ";");
+ }
+
+ private void simulateGrowthEpisode(int inEpisodeNo, GrowthEpisode inEpisode) {
+ double dt;
+
+ setBirthDeathRates(inEpisode.birthrate, inEpisode.deathrate);
+
+ while (checkStopConditionsGrowth(inEpisodeNo, inEpisode)) {
+ dt = timeToNextEvent();
+
+ currenttime += dt;
+
+ int extant = ran.nextInt(extantNodes.size());
+
+ if (eventIsBirth())
+ lineageBirth(extant, 2);
+ else
+ lineageDeath(extant);
+
+ if (extantNodes.size() < 1) {
+ System.out.println("no extant nodes: growth");
+ System.exit(0);
+ }
+ }
+ }
+
+ private void simulateConstantEpisode(int inEpisodeNo,
+ ConstantEpisode inEpisode) {
+ double dt;
+ if (inEpisode.stochastic)
+ setBirthDeathRates(inEpisode.birthdeathrate,
+ inEpisode.birthdeathrate);
+ else
+ setBirthDeathRates(inEpisode.birthdeathrate, 0.0);
+
+ while (checkStopConditionsConstant(inEpisodeNo, inEpisode)) {
+ dt = timeToNextEvent();
+
+ currenttime += dt;
+
+ int extant = ran.nextInt(extantNodes.size());
+
+ if (inEpisode.stochastic) {
+ if (eventIsBirth())
+ lineageBirth(extant, 2);
+ else
+ lineageDeath(extant);
+ } else {
+ lineageBirth(extant, 2);
+
+ extant = ran.nextInt(extantNodes.size());
+ lineageDeath(extant);
+ }
+
+ if (extantNodes.size() < 1) {
+ System.out.println("no extant nodes");
+ System.exit(0);
+ }
+ }
+ }
+
+ private void simulateExtinctionEpisode(int inEpisodeNo,
+ ExtinctionEpisode inEpisode) {
+ if (inEpisode.type == "probability") {
+ int i, n = extantNodes.size();
+ for (i = 0; i < n; i++) {
+ if (ran.nextDouble() < inEpisode.probability ? true : false)
+ lineageDeath(i);
+ }
+ } else {
+ int target, extant;
+
+ if (inEpisode.type == "fraction")
+ target = (int) ((1.0 - inEpisode.fraction) * extantNodes.size());
+ else
+ target = (int) ((extantNodes.size() - inEpisode.number));
+
+ while (extantNodes.size() > target) {
+ extant = ran.nextInt(extantNodes.size());
+ lineageDeath(extant);
+ }
+ }
+ }
+
+ private void setBirthDeathRates(double inBirthRate, double inDeathRate) {
+ if (inDeathRate == 0.0) {
+ deathrate = 0.0;
+ birthrate = inBirthRate;
+ sumrate = inBirthRate;
+ relativebirthrate = 1.0;
+ } else {
+ birthrate = inBirthRate;
+ deathrate = inDeathRate;
+ sumrate = birthrate + deathrate;
+ relativebirthrate = birthrate / sumrate;
+ }
+ }
+
+ private boolean checkStopConditionsGrowth(int inEpisodeNo,
+ GrowthEpisode inEpisode) {
+ boolean go = true;
+ if (inEpisode.stoptype == "extantstop") {
+ if (extantNodes.size() >= inEpisode.extantstop) {
+ if (inEpisodeNo == numgrowth - 1)
+ currenttime += timeToNextEvent();
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "byextantstop") {
+ long change = extantNodes.size() - lastextantlineages;
+ if (change < 0.0)
+ change *= -1.0;
+ if (change >= inEpisode.byextantstop) {
+ if (inEpisodeNo == numgrowth - 1)
+ currenttime += timeToNextEvent();
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "timestop") {
+ if (currenttime >= inEpisode.timestop) {
+ currenttime = inEpisode.timestop;
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "bytimestop") {
+ double change = Math.abs(currenttime - lastcurrenttime);
+ if (change >= inEpisode.bytimestop) {
+ go = false;
+ currenttime = lastcurrenttime + inEpisode.bytimestop;
+ }
+ } else if (inEpisode.stoptype == "multiplestop") {
+ double change = Math.abs(currenttime - lastcurrenttime);
+ if (change >= inEpisode.multipletimestop * lastepisodetime) {
+ go = false;
+ currenttime = lastcurrenttime
+ + (inEpisode.multipletimestop * lastepisodetime);
+ }
+
+ }
+ return go;
+ }
+
+ private boolean checkStopConditionsConstant(int inEpisodeNo,
+ ConstantEpisode inEpisode) {
+ boolean go = true;
+ if (inEpisode.stoptype == "extantstop") {
+ if (extantNodes.size() >= inEpisode.extantstop) {
+ if (inEpisodeNo == numconstant - 1)
+ currenttime += timeToNextEvent();
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "byextantstop") {
+ long change = extantNodes.size() - lastextantlineages;
+ if (change < 0.0)
+ change *= -1.0;
+ if (change >= inEpisode.byextantstop) {
+ if (inEpisodeNo == numconstant - 1)
+ currenttime += timeToNextEvent();
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "timestop") {
+ if (currenttime >= inEpisode.timestop) {
+ currenttime = inEpisode.timestop;
+ go = false;
+ }
+ } else if (inEpisode.stoptype == "bytimestop") {
+ double change = Math.abs(currenttime - lastcurrenttime);
+ if (change >= inEpisode.bytimestop) {
+ go = false;
+ currenttime = lastcurrenttime + inEpisode.bytimestop;
+ }
+ } else if (inEpisode.stoptype == "multiplestop") {
+ double change = Math.abs(currenttime - lastcurrenttime);
+ if (change >= inEpisode.multipletimestop * lastepisodetime) {
+ go = false;
+ currenttime = lastcurrenttime
+ + (inEpisode.multipletimestop * lastepisodetime);
+ }
+ }
+ return go;
+ }
+
+ public boolean eventIsBirth() {
+ return (ran.nextDouble() < relativebirthrate ? true : false);
+ }
+
+ public void lineageBirth(int index, int numofchild) {
+ Node node1 = new Node(extantNodes.get(index));
+ Node node2 = new Node(extantNodes.get(index));
+ extantNodes.get(index).addChild(node1);
+ extantNodes.get(index).addChild(node2);
+ node1.assocObject(BIRTHTIME, new Double(currenttime));
+ node2.assocObject(BIRTHTIME, new Double(currenttime));
+ lineageDeath(index);
+ finalT.addExternalNode(node1);
+ finalT.addExternalNode(node2);
+ extantNodes.add(node1);
+ extantNodes.add(node2);
+ }
+
+ public void lineageDeath(int index) {
+ Node x = extantNodes.get(index);
+ x.assocObject(DEATHTIME, new Double(currenttime));
+ x.setBL((Double) x.getObject(DEATHTIME)
+ - (Double) x.getObject(BIRTHTIME));
+ extantNodes.remove(index);
+ }
+
+ public double timeToNextEvent() {
+ // return -log((double)MERandom::fRan()) /
+ // (double(mPhylogeny->getNumExtantLineages()) * mSumRate);
+ // return -Math.log(ran.nextDouble()) /
+ // ((double)(finalT.getNumberOfExtantNodes()) * sumrate);
+ return -Math.log(fRan.fRan(1.0))
+ / ((double) (extantNodes.size()) * sumrate);
+ // return 5;
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 1; i++) {
+ new EpisodicTree();
+ }
+ }
+}
diff --git a/src/jade/simulate/ExtinctionEpisode.java b/src/jade/simulate/ExtinctionEpisode.java
new file mode 100644
index 0000000..b7d1a22
--- /dev/null
+++ b/src/jade/simulate/ExtinctionEpisode.java
@@ -0,0 +1,8 @@
+package jade.simulate;
+
+public class ExtinctionEpisode {
+ public double fraction;
+ public long number;
+ public double probability;
+ public String type;
+}
diff --git a/src/jade/simulate/ExtinctionFunction.java b/src/jade/simulate/ExtinctionFunction.java
new file mode 100644
index 0000000..ab6c720
--- /dev/null
+++ b/src/jade/simulate/ExtinctionFunction.java
@@ -0,0 +1,29 @@
+package jade.simulate;
+
+public class ExtinctionFunction {
+ public ExtinctionFunction(double intime, double endtime, double value){
+ start = intime;
+ end = endtime;
+ this.value = value;
+ }
+ public double getStartTime(){return start;}
+ public double getEndTime(){return end;}
+ public double getFunction(){
+ return value;
+ }
+ public void setTimes(double intime, double endtime){
+ start = intime;
+ end = endtime;
+ }
+ public String toString(){
+ String x = "";
+ x = "from "+start+" to "+end+" connection is 0 else extinction is "+value;
+ return x;
+ }
+ //start time
+ private double start;
+ //end time
+ private double end;
+ //value
+ private double value;
+}
diff --git a/src/jade/simulate/GrowthEpisode.java b/src/jade/simulate/GrowthEpisode.java
new file mode 100644
index 0000000..f0a08d3
--- /dev/null
+++ b/src/jade/simulate/GrowthEpisode.java
@@ -0,0 +1,12 @@
+package jade.simulate;
+
+public class GrowthEpisode {
+ public int extantstop;
+ public int byextantstop;
+ public double timestop;
+ public double bytimestop;
+ public double multipletimestop;
+ public double birthrate;
+ public double deathrate;
+ public String stoptype;
+}
diff --git a/src/jade/simulate/Lineage.java b/src/jade/simulate/Lineage.java
new file mode 100644
index 0000000..5ce5969
--- /dev/null
+++ b/src/jade/simulate/Lineage.java
@@ -0,0 +1,101 @@
+package jade.simulate;
+
+import jade.tree.*;
+
+import java.util.*;
+/*
+ *
+ * for use with BioGeoTree
+ *
+ * this is a node with area relationships
+ */
+public class Lineage {
+ public Lineage(Node node, int numareas){
+ ran = new Random();
+ this.node = node;
+ this.numareas = numareas;
+ curareas = new int [this.numareas];
+ curnumareas = 0;
+ }
+ public void addArea(int index){
+ curareas[index] = 1;
+ curnumareas++;
+ }
+ public void removeArea(int index){
+ curareas[index] = 0;
+ curnumareas--;
+ }
+ //return the removed area
+ public int randomRemoveArea(){
+ int rem = ran.nextInt(curnumareas);
+ int cur = 0;
+ int removed = 0;
+ for(int i=0;i<numareas;i++){
+ if(curareas[i]==1){
+ if(cur==rem){
+ curareas[i] = 0;
+ removed = i;
+ curnumareas--;
+ }
+ cur++;
+ }
+ }
+ return removed;
+ }
+ public void randomAddArea(){
+ if(curnumareas<numareas){
+ int add = ran.nextInt(numareas-curnumareas);
+ int cur = 0;
+ for(int i=0;i<numareas;i++){
+ if(curareas[i]==0){
+ if(cur==add){
+ curareas[i] = 1;
+ curnumareas++;
+ }
+ cur++;
+ }
+ }
+ }
+ }
+ //return a random area
+ public int getRandomArea(){
+ int get = ran.nextInt(curnumareas);
+ int cur = 0;
+ int gotten = 0;
+ for(int i=0;i<numareas;i++){
+ if(curareas[i]==1){
+ if(cur==get){
+ gotten = i;
+ }
+ cur++;
+ }
+ }
+ return gotten;
+ }
+ public String getString(){
+ String ret= "";
+ for(int i=0;i<numareas;i++){
+ ret += curareas[i];
+ }
+ return ret;
+ }
+ public void setAreas(int [] areas){
+ curareas = areas;
+ curnumareas = 0;
+ for(int i=0;i<numareas;i++){
+ if(curareas[i]==1){
+ curnumareas++;
+ }
+ }
+ }
+
+ public int getCurNumAreas(){return curnumareas;}
+ public int [] getAreas(){return curareas;}
+ public Node getNode(){return node;}
+
+ private int [] curareas;
+ private int numareas;//the total number of areas
+ private int curnumareas=0;//the number of areas
+ private Node node;
+ private Random ran;
+}
diff --git a/src/jade/tree/Node.java b/src/jade/tree/Node.java
new file mode 100644
index 0000000..66b0b84
--- /dev/null
+++ b/src/jade/tree/Node.java
@@ -0,0 +1,188 @@
+package jade.tree;
+
+import java.util.*;
+
+public class Node {
+ /*
+ * common associations
+ */
+ private double BL;//branch lengths
+ private double distance_to_tip;
+ private double distance_from_tip;
+ private int number;
+ private String name;
+ private Node parent;
+ private ArrayList<Node> children;
+ private ArrayList<NodeObject> assoc;
+
+ /*
+ * constructors
+ */
+ public Node(){
+ BL = 0.0;
+ distance_to_tip = 0.0;
+ distance_from_tip = 0.0;
+ number = 0;
+ name = "";
+ parent = null;
+ children = new ArrayList<Node> ();
+ assoc = new ArrayList<NodeObject>();
+ }
+
+ public Node(Node parent){
+ BL = 0.0;
+ distance_to_tip = 0.0;
+ distance_from_tip = 0.0;
+ number = 0;
+ name = "";
+ this.parent = parent;
+ children = new ArrayList<Node> ();
+ assoc = new ArrayList<NodeObject>();
+ }
+
+ public Node(double BL, int number, String name, Node parent){
+ this.BL = BL;
+ distance_to_tip = 0.0;
+ distance_from_tip = 0.0;
+ this.number = number;
+ this.name = name;
+ this.parent = parent;
+ children = new ArrayList<Node> ();
+ assoc = new ArrayList<NodeObject>();
+ }
+
+ /*
+ * public methods
+ */
+
+ public Node [] getChildrenArr(){return (Node[])children.toArray();}
+
+ public ArrayList<Node> getChildren(){return children;}
+
+ public boolean isExternal(){
+ if(children.size()<1)
+ return true;
+ else
+ return false;
+ }
+
+ public boolean isInternal(){
+ if(children.size()<1)
+ return false;
+ else
+ return true;
+ }
+
+ public boolean isTheRoot(){
+ if(parent == null)
+ return true;
+ else
+ return false;
+ }
+
+ public boolean hasParent(){
+ if(parent == null)
+ return false;
+ else
+ return true;
+ }
+
+ public void setParent(Node p){this.parent = p;}
+
+ public int getNumber(){return number;}
+
+ public void setNumber(int n){number = n;}
+
+ public double getBL(){return BL;}
+
+ public void setBL(double b){BL=b;}
+
+ public boolean hasChild(Node test){
+ return children.contains((Node)test);
+ }
+
+ public boolean addChild(Node c){
+ if(hasChild(c)==false){
+ children.add(c);
+ c.setParent(this);
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ public boolean removeChild(Node c){
+ if(hasChild(c)==true){
+ children.remove(c);
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ public Node getChild(int c){return children.get(c);}
+
+ public void setName(String s){name = s;}
+
+ public String getName(){
+ //if(name != ""){
+ return name;
+ //}else{
+ // return this.getNewick(false);
+ //}
+ }
+
+ public String getNewick(boolean bl){
+ String ret = "";
+ for(int i=0;i<this.getChildCount();i++){
+ if(i==0)
+ ret = ret+"(";
+ ret = ret+this.getChild(i).getNewick(bl);
+ if(bl==true)
+ ret = ret +":"+this.getChild(i).getBL();
+ if(i == this.getChildCount()-1)
+ ret =ret +")";
+ else
+ ret = ret+",";
+ }
+ if(name!=null)
+ ret = ret + name;
+ return ret;
+ }
+
+ public Node getParent(){return parent;}
+
+ public int getChildCount(){return children.size();}
+
+ public double getDistanceFromTip(){return distance_from_tip;}
+
+ public void setDistanceFromTip(double inh){distance_from_tip = inh;}
+
+ public double getDistanceToTip(){return distance_to_tip;}
+
+ public void setDistanceToTip(double inh){distance_to_tip = inh;}
+
+ public void assocObject(String name, Object obj){
+ boolean test = false;
+ for(int i=0;i<assoc.size();i++){
+ if(assoc.get(i).getName().compareTo(name)==0){
+ test = true;
+ assoc.get(i).setObject(obj);
+ }
+ }
+ if(test == false){
+ NodeObject no = new NodeObject(name, obj);
+ assoc.add(no);
+ }
+ }
+
+ public Object getObject(String name){
+ Object a = null;
+ for(int i=0;i<assoc.size();i++){
+ if(assoc.get(i).getName().compareTo(name)==0){
+ a = assoc.get(i).getObject();
+ }
+ }
+ return a;
+ }
+}
diff --git a/src/jade/tree/NodeObject.java b/src/jade/tree/NodeObject.java
new file mode 100644
index 0000000..bdb30cc
--- /dev/null
+++ b/src/jade/tree/NodeObject.java
@@ -0,0 +1,16 @@
+package jade.tree;
+
+public class NodeObject {
+ private String name;
+ private Object obj;
+
+ public NodeObject(String name, Object obj){
+ this.name = name;
+ this.obj = obj;
+ }
+
+ public Object getObject(){return obj;}
+ public String getName(){return name;}
+ public void setObject(Object obj){this.obj = obj;}
+ public void setName(String name){ this.name = name;}
+}
diff --git a/src/jade/tree/Tree.java b/src/jade/tree/Tree.java
new file mode 100644
index 0000000..c1a0a07
--- /dev/null
+++ b/src/jade/tree/Tree.java
@@ -0,0 +1,382 @@
+package jade.tree;
+
+import java.util.*;
+
+public class Tree {
+ /*
+ * private
+ */
+ private Node root;
+
+ private ArrayList<Node> nodes;
+
+ private ArrayList<Node> internalNodes;
+
+ private ArrayList<Node> externalNodes;
+
+ private ArrayList<TreeObject> assoc;
+
+ private int internalNodeCount;
+
+ private int externalNodeCount;
+
+ /*
+ * constructors
+ */
+ public Tree() {
+ root = null;
+ assoc = new ArrayList<TreeObject>();
+ processRoot();
+ }
+
+ public Tree(Node root) {
+ this.root = root;
+ assoc = new ArrayList<TreeObject>();
+ processRoot();
+ }
+
+ public void processRoot() {
+ nodes = new ArrayList<Node>();
+ internalNodes = new ArrayList<Node>();
+ externalNodes = new ArrayList<Node>();
+ internalNodeCount = 0;
+ externalNodeCount = 0;
+ if (root == null)
+ return;
+ postOrderProcessRoot(root);
+ }
+
+ public void addExternalNode(Node tn) {
+ externalNodes.add(tn);
+ externalNodeCount = externalNodes.size();
+ nodes.add(tn);
+ }
+
+ public void addInternalNode(Node tn) {
+ internalNodes.add(tn);
+ internalNodeCount = internalNodes.size();
+ //to nodes
+ nodes.add(tn);
+ }
+
+ public void addExternalNode(Node tn, int num) {
+ externalNodes.add(tn);
+ externalNodeCount = externalNodes.size();
+ //to nodes
+ nodes.add(tn);
+ tn.setNumber(num);
+ }
+
+ public void addInternalNode(Node tn, int num) {
+ internalNodes.add(tn);
+ internalNodeCount = internalNodes.size();
+ nodes.add(tn);
+ tn.setNumber(num);
+ }
+
+ public Node getExternalNode(int num) {
+ return externalNodes.get(num);
+ }
+
+ public Node getExternalNode(String name) {
+ Node retNode = null;
+ Iterator go = externalNodes.iterator();
+ while (go.hasNext()) {
+ Node ne = (Node) go.next();
+ if (ne.getName().compareTo(name) == 0) {
+ retNode = ne;
+ break;
+ }
+ }
+ return retNode;
+ }
+
+ public Node getInternalNode(int num) {
+ return internalNodes.get(num);
+ }
+
+ public Node getInternalNode(String name) {
+ Node retNode = null;
+ Iterator go = internalNodes.iterator();
+ while (go.hasNext()) {
+ Node ne = (Node) go.next();
+ if (ne.getName().compareTo(name) == 0) {
+ retNode = ne;
+ break;
+ }
+ }
+ return retNode;
+ }
+
+ public int getExternalNodeCount(){return externalNodes.size();}
+ public int getInternalNodeCount(){return internalNodes.size();}
+
+ public Node getRoot() {
+ return root;
+ }
+
+ public void setRoot(Node root) {
+ this.root = root;
+ }
+
+ public void assocObject(String name, Object obj) {
+ TreeObject no = new TreeObject(name, obj);
+ assoc.add(no);
+ }
+
+ public Object getObject(String name) {
+ Object a = null;
+ for (int i = 0; i < assoc.size(); i++) {
+ if (assoc.get(i).getName().compareTo(name) == 0) {
+ a = assoc.get(i);
+ }
+ }
+ return a;
+ }
+
+ //need to check
+ public void unRoot(Node inRoot){
+ processRoot();
+ if (this.getRoot().getChildCount() < 3) {
+ tritomyRoot(inRoot);
+ }
+ processRoot();
+ }
+
+ /*
+ * just need to verify that the rerooting treats the branch lengths correctly
+ */
+ public void reRoot(Node inRoot) {
+ processRoot();
+ if (this.getRoot().getChildCount() < 3) {
+ tritomyRoot(inRoot);
+ }
+ //System.out.println(inRoot.getBL());
+ if (inRoot == this.getRoot()) {
+ System.err.println("you asked to root at the current root");
+ } else {
+ Node tempParent = inRoot.getParent();
+ Node newRoot = new Node(tempParent);
+ newRoot.addChild(inRoot);
+ inRoot.setParent(newRoot);
+ tempParent.removeChild(inRoot);
+ tempParent.addChild(newRoot);
+ newRoot.setParent(tempParent);
+ newRoot.setBL(inRoot.getBL() / 2);
+ inRoot.setBL(inRoot.getBL() / 2);
+ ProcessReRoot(newRoot);
+ setRoot(newRoot);
+ processRoot();
+ }
+ }
+
+ public void tritomyRoot(Node toberoot) {
+ Node curroot = this.getRoot();
+ if (toberoot == null) {
+ if (curroot.getChild(0).isInternal()) {
+ Node currootCH = curroot.getChild(0);
+ double nbl = currootCH.getBL();
+ curroot.getChild(1).setBL(curroot.getChild(1).getBL() + nbl);
+ curroot.removeChild(currootCH);
+ for (int i = 0; i < currootCH.getChildCount(); i++) {
+ curroot.addChild(currootCH.getChild(i));
+ //currootCH.getChild(i).setParent(curroot);
+ }
+ } else {
+ Node currootCH = curroot.getChild(1);
+ double nbl = currootCH.getBL();
+ curroot.getChild(0).setBL(curroot.getChild(0).getBL() + nbl);
+ curroot.removeChild(currootCH);
+ for (int i = 0; i < currootCH.getChildCount(); i++) {
+ curroot.addChild(currootCH.getChild(i));
+ //currootCH.getChild(i).setParent(curroot);
+ }
+ }
+ } else {
+ if (curroot.getChild(1) == toberoot) {
+ Node currootCH = curroot.getChild(0);
+ double nbl = currootCH.getBL();
+ curroot.getChild(1).setBL(curroot.getChild(1).getBL() + nbl);
+ curroot.removeChild(currootCH);
+ for (int i = 0; i < currootCH.getChildCount(); i++) {
+ curroot.addChild(currootCH.getChild(i));
+ //currootCH.getChild(i).setParent(curroot);
+ }
+ } else {
+ Node currootCH = curroot.getChild(1);
+ double nbl = currootCH.getBL();
+ curroot.getChild(0).setBL(curroot.getChild(0).getBL() + nbl);
+ curroot.removeChild(currootCH);
+ for (int i = 0; i < currootCH.getChildCount(); i++) {
+ curroot.addChild(currootCH.getChild(i));
+ //currootCH.getChild(i).setParent(curroot);
+ }
+ }
+ }
+ }
+
+ public Node getMRCA(String [] innodes){
+ Node mrca = null;
+ if(innodes.length == 1)
+ return this.getExternalNode(innodes[0]);
+ else{
+ ArrayList <String> outgroup = new ArrayList<String>();
+ for(int i=0;i<innodes.length;i++){outgroup.add(innodes[i]);}
+ Node cur1 = this.getExternalNode(outgroup.get(0));
+ outgroup.remove(0);
+ Node cur2 = null;
+ Node tempmrca = null;
+ while(outgroup.size()>0){
+ cur2 = this.getExternalNode(outgroup.get(0));
+ outgroup.remove(0);
+ tempmrca = getMRCATraverse(cur1,cur2);
+ cur1 = tempmrca;
+ }
+ mrca = cur1;
+ }
+ return mrca;
+ }
+
+ public Node getMRCA(ArrayList<String> innodes){
+ Node mrca = null;
+ if(innodes.size() == 1)
+ return this.getExternalNode(innodes.get(0));
+ else{
+ ArrayList <String> outgroup = new ArrayList<String>();
+ for(int i=0;i<innodes.size();i++){outgroup.add(innodes.get(i));}
+ Node cur1 = this.getExternalNode(outgroup.get(0));
+ outgroup.remove(0);
+ Node cur2 = null;
+ Node tempmrca = null;
+ while(outgroup.size()>0){
+ cur2 = this.getExternalNode(outgroup.get(0));
+ outgroup.remove(0);
+ tempmrca = getMRCATraverse(cur1,cur2);
+ cur1 = tempmrca;
+ }
+ mrca = cur1;
+ }
+ return mrca;
+ }
+
+ private void ProcessReRoot(Node node) {
+ if (node.isTheRoot() || node.isExternal()) {
+ return;
+ }
+ if (node.getParent() != null) {
+ ProcessReRoot(node.getParent());
+ }
+ // Exchange branch label, length et cetera
+ exchangeInfo(node.getParent(), node);
+ // Rearrange topology
+ Node parent = node.getParent();
+ node.addChild(parent);
+ parent.removeChild(node);
+ parent.setParent(node);
+ }
+
+ /*
+ * swap info
+ */
+ private void exchangeInfo(Node node1, Node node2) {
+ String swaps;
+ double swapd;
+ swaps = node1.getName();
+ node1.setName(node2.getName());
+ node2.setName(swaps);
+
+ swapd = node1.getBL();
+ node1.setBL(node2.getBL());
+ node2.setBL(swapd);
+ }
+
+ private void postOrderProcessRoot(Node node) {
+ if (node == null)
+ return;
+ if (node.getChildCount() > 0) {
+ for (int i = 0; i < node.getChildCount(); i++) {
+ postOrderProcessRoot(node.getChild(i));
+ }
+ }
+ if (node.isExternal()) {
+ addExternalNode(node, externalNodeCount);
+ } else {
+ addInternalNode(node, internalNodeCount);
+ }
+ }
+
+ /*
+ * prune external node
+ */
+ public void pruneExternalNode(Node node){
+ if(node.isInternal()){
+ return;
+ }
+ /*
+ * how this works
+ *
+ * get the parent = parent
+ * get the parent of the parent = mparent
+ * remove parent from mparent
+ * add !node from parent to mparent
+ *
+ * doesn't yet take care if node.parent == root
+ * or polytomy
+ */
+ double bl = 0;
+ Node parent = node.getParent();
+ Node other = null;
+ for(int i=0;i<parent.getChildCount();i++){
+ if(parent.getChild(i)!=node){
+ other = parent.getChild(i);
+ }
+ }
+ bl = other.getBL()+parent.getBL();
+ Node mparent = parent.getParent();
+ if(mparent != null){
+ mparent.addChild(other);
+ other.setBL(bl);
+ for(int i=0;i<mparent.getChildCount();i++){
+ if(mparent.getChild(i)==parent){
+ mparent.removeChild(parent);
+ break;
+ }
+ }
+ }
+ this.processRoot();
+ }
+
+ /*
+ * get the MRCA of the array of strings
+ *
+ */
+
+ private Node getMRCATraverse(Node curn1, Node curn2) {
+ Node mrca = null;
+ //get path to root for first node
+ ArrayList<Node> path1 = new ArrayList<Node>();
+ Node parent = curn1;
+ path1.add(parent);
+ while (parent != null) {
+ path1.add(parent);
+ if (parent.getParent() != null)
+ parent = parent.getParent();
+ else
+ break;
+ }
+ //find first match between this node and the first one
+ parent = curn2;
+ boolean x = true;
+ while (x == true) {
+ for (int i = 0; i < path1.size(); i++) {
+ if (parent == path1.get(i)) {
+ mrca = parent;
+ x = false;
+ break;
+ }
+ }
+ parent = parent.getParent();
+ }
+ return mrca;
+ }
+}
diff --git a/src/jade/tree/TreeFileReader.java b/src/jade/tree/TreeFileReader.java
new file mode 100644
index 0000000..b1af4fa
--- /dev/null
+++ b/src/jade/tree/TreeFileReader.java
@@ -0,0 +1,5 @@
+package jade.tree;
+
+public class TreeFileReader {
+
+}
diff --git a/src/jade/tree/TreeObject.java b/src/jade/tree/TreeObject.java
new file mode 100644
index 0000000..ca3d0ff
--- /dev/null
+++ b/src/jade/tree/TreeObject.java
@@ -0,0 +1,16 @@
+package jade.tree;
+
+public class TreeObject {
+ private String name;
+ private Object obj;
+
+ public TreeObject(String name, Object obj){
+ this.name = name;
+ this.obj = obj;
+ }
+
+ public Object getObject(){return obj;}
+ public String getName(){return name;}
+ public void setObject(Object obj){this.obj = obj;}
+ public void setName(String name){ this.name = name;}
+}
diff --git a/src/jade/tree/TreePrinter.java b/src/jade/tree/TreePrinter.java
new file mode 100644
index 0000000..cdb3f1c
--- /dev/null
+++ b/src/jade/tree/TreePrinter.java
@@ -0,0 +1,333 @@
+package jade.tree;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+
+public class TreePrinter {
+ public TreePrinter() {
+ }
+
+ public void reportASCII(Tree tree, PrintWriter out) {
+ printASCII(tree, out);
+ out.println();
+ }
+
+ private double proportion;
+
+ private int minLength;
+
+ private boolean[] umbrella;
+
+ private int[] position;
+
+ private int numExternalNodes;
+
+ private int numInternalNodes;
+
+ private int numBranches;
+
+ // private NumberFormat nf;
+
+ // Print picture of current tree in ASCII
+ private void printASCII(Tree tree, PrintWriter out) {
+ tree.processRoot();
+
+ numExternalNodes = tree.getExternalNodeCount();
+ numInternalNodes = tree.getInternalNodeCount();
+ numBranches = numInternalNodes + numExternalNodes - 1;
+
+ umbrella = new boolean[numExternalNodes];
+ position = new int[numExternalNodes];
+
+ minLength = (Integer.toString(numBranches)).length() + 1;
+
+ int MAXCOLUMN = 40;
+ Node root = tree.getRoot();
+ if (root.getDistanceToTip() == 0.0) {
+ TreeUtils.setDistanceToTips(root);
+ }
+ proportion = (double) MAXCOLUMN / root.getDistanceToTip();
+ // proportion = (double) MAXCOLUMN/0.1;
+
+ for (int n = 0; n < numExternalNodes; n++) {
+ umbrella[n] = false;
+ }
+
+ position[0] = 1;
+ for (int i = root.getChildCount() - 1; i > -1; i--) {
+ printNodeInASCII(out, root.getChild(i), 1, i, root.getChildCount());
+ if (i != 0) {
+ putCharAtLevel(out, 0, '|');
+ out.println();
+ }
+ }
+ }
+
+ private void printNodeInASCII(PrintWriter out, Node node, int level, int m,
+ int maxm) {
+ position[level] = (int) (node.getBL() * proportion);
+
+ if (position[level] < minLength) {
+ position[level] = minLength;
+ }
+
+ if (node.isExternal()) // external branch
+ {
+ if (m == maxm - 1) {
+ umbrella[level - 1] = true;
+ }
+
+ printlnNodeWithNumberAndLabel(out, node, level);
+
+ if (m == 0) {
+ umbrella[level - 1] = false;
+ }
+ } else // internal branch
+ {
+ for (int n = node.getChildCount() - 1; n > -1; n--) {
+ printNodeInASCII(out, node.getChild(n), level + 1, n, node
+ .getChildCount());
+
+ if (m == maxm - 1 && n == node.getChildCount() / 2) {
+ umbrella[level - 1] = true;
+ }
+
+ if (n != 0) {
+ if (n == node.getChildCount() / 2) {
+ printlnNodeWithNumberAndLabel(out, node, level);
+ } else {
+ for (int i = 0; i < level + 1; i++) {
+ if (umbrella[i]) {
+ putCharAtLevel(out, i, '|');
+ } else {
+ putCharAtLevel(out, i, ' ');
+ }
+ }
+ out.println();
+ }
+ }
+
+ if (m == 0 && n == node.getChildCount() / 2) {
+ umbrella[level - 1] = false;
+ }
+ }
+ }
+ }
+
+ private void printlnNodeWithNumberAndLabel(PrintWriter out, Node node,
+ int level) {
+ for (int i = 0; i < level - 1; i++) {
+ if (umbrella[i]) {
+ putCharAtLevel(out, i, '|');
+ } else {
+ putCharAtLevel(out, i, ' ');
+ }
+ }
+
+ putCharAtLevel(out, level - 1, '+');
+
+ int branchNumber;
+ if (node.isExternal()) {
+ branchNumber = node.getNumber() + 1;
+ } else {
+ branchNumber = node.getNumber() + 1 + numExternalNodes;
+ }
+
+ String numberAsString = Integer.toString(branchNumber);
+
+ int numDashs = position[level] - numberAsString.length();
+ for (int i = 0; i < numDashs; i++) {
+ out.print('-');
+ }
+ out.print(numberAsString);
+
+ if (node.isExternal()) {
+ out.println(" " + node.getName());
+ } else {
+ if (!node.getName().equals("")) {
+ out.print("(" + node.getName() + ")");
+ }
+ out.println();
+ }
+ }
+
+ private void putCharAtLevel(PrintWriter out, int level, char c) {
+ int n = position[level] - 1;
+ for (int i = 0; i < n; i++) {
+ out.print(' ');
+ }
+ out.print(c);
+ }
+/*
+ private void displayIntegerWhite(PrintWriter out, int maxNum) {
+ int lenMaxNum = Integer.toString(maxNum).length();
+
+ multiplePrint(out, ' ', lenMaxNum);
+ }
+
+ private int displayDecimalASCII(PrintWriter out, double number, int width) {
+ String s = getDecimalStringASCII(number, width);
+
+ out.print(s);
+
+ return s.length();
+ }
+
+ private void displayLabel(PrintWriter out, String label, int width) {
+ int len = label.length();
+
+ if (len == width) {
+ // Print as is
+ out.print(label);
+ } else if (len < width) {
+ // fill rest with spaces
+ out.print(label);
+ multiplePrint(out, ' ', width - len);
+ } else {
+ // Print first width characters
+ for (int i = 0; i < width; i++) {
+ out.print(label.charAt(i));
+ }
+ }
+ }
+*/
+ /**
+ * print integer, aligned to a reference number, (introducing space at the
+ * left side)
+ *
+ * @param out
+ * output stream
+ * @param num
+ * number to be printed
+ * @param maxNum
+ * reference number
+ */
+ /*
+ private void displayInteger(PrintWriter out, int num, int maxNum) {
+ int lenNum = Integer.toString(num).length();
+ int lenMaxNum = Integer.toString(maxNum).length();
+
+ if (lenNum < lenMaxNum) {
+ multiplePrint(out, ' ', lenMaxNum - lenNum);
+ }
+ out.print(num);
+ }
+
+ private void multiplePrint(PrintWriter out, char c, int num) {
+ for (int i = 0; i < num; i++) {
+ out.print(c);
+ }
+ }
+
+ private synchronized String getDecimalStringASCII(double number, int width) {
+ nf.setMinimumFractionDigits(width);
+ nf.setMaximumFractionDigits(width);
+
+ return nf.format(number);
+ }
+ */
+ /*
+ *
+ *
+ *
+ *
+ * printing newick format
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+ private Tree inTree;
+
+ private NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
+
+ private String nhString;
+
+ public String printNH(Tree intree) {
+ inTree = intree;
+ nhString = "";
+ PrintStream ps = new PrintStream(System.out);
+ printNH(new PrintWriter(ps), inTree.getRoot(), true, true, 0, false);
+ return nhString;
+ }
+ /*
+ private void printNH(PrintWriter out, Node node, boolean printLengths,
+ boolean printInternalLabels) {
+ printNH(out, node, printLengths, printInternalLabels, 0, true);
+ }*/
+
+ private int printNH(PrintWriter out, Node node, boolean printLengths,
+ boolean printInternalLabels, int column, boolean breakLines) {
+ if (breakLines)
+ column = breakLine(out, column);
+ if (!node.isExternal()) {
+ out.print("(");
+ nhString = nhString + "(";
+ column++;
+
+ for (int i = 0; i < node.getChildCount(); i++) {
+ if (i != 0) {
+ out.print(",");
+ nhString = nhString + ",";
+ column++;
+ }
+
+ column = printNH(out, node.getChild(i), printLengths,
+ printInternalLabels, column, breakLines);
+ }
+
+ out.print(")");
+ nhString = nhString + ")";
+ column++;
+ }
+ if (!node.isTheRoot()) {
+ if (node.isExternal() || printInternalLabels) {
+ if (breakLines)
+ column = breakLine(out, column);
+
+ String id = node.getName();
+ out.print(id);
+ nhString = nhString + id;
+ column += id.length();
+ }
+ if (printLengths) {
+ out.print(":");
+ nhString = nhString + ":";
+ column++;
+ if (breakLines)
+ column = breakLine(out, column);
+ column += displayDecimalNH(out, node.getBL(), 8);
+ }
+ }
+ return column;
+ }
+
+ private int breakLine(PrintWriter out, int column) {
+ if (column > 70) {
+ out.println();
+ nhString = nhString + "\n";
+ column = 0;
+ }
+
+ return column;
+ }
+
+ private int displayDecimalNH(PrintWriter out, double number, int width) {
+ String s = getDecimalStringNH(number, width);
+ out.print(s);
+ nhString = nhString + s;
+ return s.length();
+ }
+
+ private synchronized String getDecimalStringNH(double number, int width) {
+ nf.setMinimumFractionDigits(width);
+ nf.setMaximumFractionDigits(width);
+ return nf.format(number);
+ }
+}
diff --git a/src/jade/tree/TreeReader.java b/src/jade/tree/TreeReader.java
new file mode 100644
index 0000000..abdc634
--- /dev/null
+++ b/src/jade/tree/TreeReader.java
@@ -0,0 +1,158 @@
+/**
+ *
+ */
+package jade.tree;
+
+/**
+ * @author smitty
+ *
+ */
+public class TreeReader {
+ private String treeString;
+
+ private Tree tree;
+
+ /*
+ * constructor
+ */
+ public TreeReader() {
+ }
+
+ /*
+ * read from the newick format
+ */
+ public TreeReader(String tree) {
+ treeString = tree;
+ }
+
+ public void setTree(String tree) {
+ treeString = tree;
+ }
+
+ public Tree getTree(){return tree;}
+
+ public Tree readTree() {
+ tree = new Tree();
+ String pb = treeString;
+ int x = 0;
+ char nextChar = pb.charAt(x);
+ boolean start = true;
+ boolean keepGoing = true;
+ Node currNode = new Node();
+ while (keepGoing == true) {
+ if (nextChar == '(') {
+ if (start == true) {
+ Node root = new Node();
+ tree.setRoot(root);
+ currNode = root;
+ start = false;
+ } else {
+ Node newNode = new Node(currNode);
+ currNode.addChild(newNode);
+ currNode = newNode;
+ }
+ } else if (nextChar == ',') {
+ currNode = currNode.getParent();
+ } else if (nextChar == ')') {
+ currNode = currNode.getParent();
+ x++;
+ nextChar = pb.charAt(x);
+ String nam = "";
+ boolean goingName = true;
+ if (nextChar == ',' || nextChar == ')' || nextChar == ':'
+ || nextChar == ';'|| nextChar == '[') {
+ goingName = false;
+ }
+ while (goingName == true) {
+ nam = nam + nextChar;
+ x++;
+ nextChar = pb.charAt(x);
+ if (nextChar == ',' || nextChar == ')' || nextChar == ':'
+ || nextChar == ';'|| nextChar == '[') {
+ goingName = false;
+ break;
+ }
+ }// work on edge
+ currNode.setName(nam);
+ // currNode.getEdge(currNode.getParent()).setLength(Double.parseDouble(edgeL));
+ // System.out.println(nam);
+ // pb.unread(nextChar);
+ x--;
+ pb.charAt(x);
+ } else if (nextChar == ';') {
+ keepGoing = false;
+ } else if (nextChar == ':') {
+ x++;
+ nextChar = pb.charAt(x);
+ String edgeL = "";
+ boolean goingName = true;
+ while (goingName == true) {
+ edgeL = edgeL + nextChar;
+ x++;
+ nextChar = pb.charAt(x);
+ if (nextChar == ',' || nextChar == ')' || nextChar == ':'
+ || nextChar == ';'|| nextChar == '[') {
+ goingName = false;
+ break;
+ }
+ }// work on edge
+ currNode.setBL(Double.parseDouble(edgeL));
+ // currNode.getEdge(currNode.getParent()).setLength(Double.parseDouble(edgeL));
+ // System.out.println(Double.parseDouble(edgeL));
+ // pb.unread(nextChar);
+ x--;
+ pb.charAt(x);
+ }
+ //note
+ else if (nextChar == '[') {
+ x++;
+ nextChar = pb.charAt(x);
+ String note = "";
+ boolean goingNote = true;
+ while (goingNote == true) {
+ note = note + nextChar;
+ x++;
+ nextChar = pb.charAt(x);
+ if (nextChar == ']' ) {
+ goingNote = false;
+ break;
+ }
+ }// work on note
+ //currNode.setBL(Double.parseDouble(edgeL));
+ //x--;
+ pb.charAt(x);
+ } else if (nextChar == ' ') {
+
+ }
+ // external named node
+ else {
+ Node newNode = new Node(currNode);
+ currNode.addChild(newNode);
+ currNode = newNode;
+ String nodeName = "";
+ boolean goingName = true;
+ while (goingName == true) {
+ nodeName = nodeName + nextChar;
+ x++;
+ nextChar = pb.charAt(x);
+ if (nextChar == ',' || nextChar == ')' || nextChar == ':' || nextChar == '[') {
+ goingName = false;
+ break;
+ }
+ }
+ newNode.setName(nodeName);
+ // System.out.println(nodeName);
+ // pb.unread(nextChar);
+ x--;
+ pb.charAt(x);
+ }
+ if (x < pb.length() - 1)//added
+ x++;
+ //
+ nextChar = pb.charAt(x);
+ //System.out.println(nextChar);
+ }
+ tree.processRoot();
+ return tree;
+ }
+}
diff --git a/src/jade/tree/TreeSimulator.java b/src/jade/tree/TreeSimulator.java
new file mode 100644
index 0000000..696e365
--- /dev/null
+++ b/src/jade/tree/TreeSimulator.java
@@ -0,0 +1,8 @@
+package jade.tree;
+
+public class TreeSimulator {
+ public TreeSimulator(){
+
+ }
+
+}
diff --git a/src/jade/tree/TreeUtils.java b/src/jade/tree/TreeUtils.java
new file mode 100644
index 0000000..85ec961
--- /dev/null
+++ b/src/jade/tree/TreeUtils.java
@@ -0,0 +1,67 @@
+package jade.tree;
+
+public class TreeUtils {
+ /*
+ * setting the height of nodes
+ */
+ public static void setDistanceToTips(Node root) {
+ setDistanceToTips(root, getGreatestDistance(root));
+ }
+
+ private static double getGreatestDistance(Node inNode) {
+ double distance = 0.0;
+ if (inNode.isInternal()) {
+ if (inNode.isTheRoot()) {
+ distance = inNode.getBL();
+ }
+ double posmax = 0.0;
+ for (int i = 0; i < inNode.getChildCount(); i++) {
+ double posmax2 = getGreatestDistance(inNode.getChild(i));
+ if (posmax2 > posmax)
+ posmax = posmax2;
+ }
+ distance += posmax;
+ return distance;
+ } else
+ return inNode.getBL();
+ }
+
+ private static void setDistanceToTips(Node inNode, double newHeight) {
+ if (inNode.isTheRoot() == false) {
+ newHeight -= inNode.getBL();
+ inNode.setDistanceToTip(newHeight);
+ } else {
+ inNode.setDistanceToTip(newHeight);
+ }
+ for (int i = 0; i < inNode.getChildCount(); i++) {
+ setDistanceToTips(inNode.getChild(i), newHeight);
+ }
+ }
+
+ public static void setDistanceFromTip(Tree tree) {
+ for (int i = 0; i < tree.getExternalNodeCount(); i++) {
+ Node cur = tree.getExternalNode(i);
+ double curh = 0.0;
+ while (cur != null) {
+ curh += cur.getBL();
+ cur = cur.getParent();
+ }
+ tree.getExternalNode(i).setDistanceFromTip(curh);
+ }
+ }
+
+ public static void setDistanceToTip(Tree tree){
+ for (int i = 0; i < tree.getExternalNodeCount(); i++) {
+ double curh = 0.0;
+ Node cur = tree.getExternalNode(i);
+ cur.setDistanceToTip(curh);
+ while (cur != null) {
+ curh += cur.getBL();
+ if(cur.getDistanceToTip()<curh)
+ cur.setDistanceToTip(curh);
+ cur = cur.getParent();
+ }
+
+ }
+ }
+}
diff --git a/src/jebl/evolution/align/Align.java b/src/jebl/evolution/align/Align.java
new file mode 100644
index 0000000..6b13f48
--- /dev/null
+++ b/src/jebl/evolution/align/Align.java
@@ -0,0 +1,253 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+public abstract class Align {
+
+ Scores sub; // scores matrix
+ Scores freeGapsSub; // scores matrix when free end gaps
+ float d; // gap cost
+ String seq1 = null;
+ String seq2 = null; // the sequences
+ int n = 0;
+ int m = 0; // their lengths
+ Traceback B0; // the starting point of the traceback
+
+ public Align(Scores sub, float d) {
+ setGapOpen(d);
+ setScores(sub);
+ }
+
+ /**
+ * Performs the alignment, abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ /**
+ * Initialises the matrices for the alignment.
+ *
+ * @param seq1
+ * @param seq2
+ */
+ public abstract void prepareAlignment(String seq1, String seq2);
+
+ public void setGapOpen(float d) {
+ this.d = d;
+ }
+
+ public void setScores(Scores sub) {
+ this.sub = sub;
+ freeGapsSub = Scores.duplicate(sub);
+ for (int i = 0; i < 127; i++) {
+ freeGapsSub.score['-'][i] = 0;
+ freeGapsSub.score[i]['-'] = 0;
+ }
+ }
+
+ /**
+ * @return two-element array containing an alignment with maximal score
+ */
+ public String[] getMatch() {
+
+ char[] sq1 = seq1.toCharArray();
+ char[] sq2 = seq2.toCharArray();
+
+ StringBuilder res1 = new StringBuilder();
+ StringBuilder res2 = new StringBuilder();
+ Traceback tb = B0;
+
+ int i = tb.i, j = tb.j;
+ while ((tb = next(tb)) != null) {
+ if (i == tb.i) {
+ res1.append('-');
+ } else {
+ res1.append(sq1[i - 1]);
+ }
+ if (j == tb.j) {
+ res2.append('-');
+ } else {
+ res2.append(sq2[j - 1]);
+ }
+ i = tb.i;
+ j = tb.j;
+ }
+ return new String[]{res1.reverse().toString(), res2.reverse().toString()};
+ }
+
+ /**
+ * @param val
+ * @return float value of string val
+ */
+ public String formatScore(float val) {
+ return Float.toString(val);
+ }
+
+ /**
+ * Print the score, the F matrix, and the alignment
+ *
+ * @param out output to print to
+ * @param msg message printed at start
+ * @param outputFMatrix print the score matrix
+ */
+ public void doMatch(Output out, String msg, boolean outputFMatrix) {
+ out.println(msg + ":");
+ out.println("Score = " + getScore());
+ if (outputFMatrix) {
+ out.println("The F matrix:");
+ printf(out);
+ }
+ out.println("An optimal alignment:");
+ String[] match = getMatch();
+ out.println(match[0]);
+ out.println(match[1]);
+
+ int[] counts = matchCounts(match);
+
+ out.println("matchs=" + counts[0] + " mismatchs=" + counts[1] + " gaps=" + counts[2]);
+ out.println("percent identity=" + Math.round((double) counts[0] * 1000 / match[0].length()) / 10.0 + "%");
+
+ }
+
+ private int[] matchCounts(String[] match) {
+ int[] matchCounts = new int[3];
+ for (int i = 0; i < match[0].length(); i++) {
+ char c1 = match[0].charAt(i);
+ char c2 = match[1].charAt(i);
+
+ if (c1 == c2) {
+ matchCounts[0] += 1;
+ } else if (c1 != '-' && c2 != '-') {
+ matchCounts[1] += 1;
+ } else {
+ matchCounts[2] += 1;
+ }
+ }
+ return matchCounts;
+ }
+
+ public void traceback(TracebackPlotter plotter) {
+
+ plotter.newTraceBack(seq1, seq2);
+
+ Traceback tb = B0;
+ while (tb != null) {
+ plotter.traceBack(tb);
+ tb = next(tb);
+ }
+ plotter.finishedTraceBack();
+ }
+
+ /**
+ * Print the score and the alignment
+ *
+ * @param out output to print to
+ * @param msg msg printed at the start
+ */
+ public void doMatch(Output out, String msg) {
+ doMatch(out, msg, false);
+ }
+
+ /**
+ * Get the next state in the traceback
+ *
+ * @param tb current Traceback
+ * @return next Traceback
+ */
+ public Traceback next(Traceback tb) {
+ return tb;
+ } // dummy implementation for the `smart' algs.
+
+ /**
+ * @return the score of the best alignment
+ */
+ public abstract float getScore();
+
+ /**
+ * Print the matrix (matrices) used to compute the alignment
+ *
+ * @param out output to print to
+ */
+ public abstract void printf(Output out);
+
+ // auxillary static functions
+
+ static float max(float x1, float x2) {
+ return (x1 > x2 ? x1 : x2);
+ }
+
+ static int maxi(int x1, int x2) {
+ return (x1 > x2 ? x1 : x2);
+ }
+
+ static float max(float x1, float x2, float x3) {
+ return max(x1, max(x2, x3));
+ }
+
+ static float max(float x1, float x2, float x3, float x4) {
+ return max(max(x1, x2), max(x3, x4));
+ }
+
+ /**
+ * @param s string to pad
+ * @param width width to pad to
+ * @return string padded to specified width with space chars.
+ */
+ static String padLeft(String s, int width) {
+ int filler = width - s.length();
+ if (filler > 0) { // and therefore width > 0
+ StringBuilder res = new StringBuilder(width);
+ for (int i = 0; i < filler; i++)
+ res.append(' ');
+ return res.append(s).toString();
+ } else
+ return s;
+ }
+
+ // PRIVATE METHODS
+
+ /**
+ * Strips the given string of all characters that are not recognized sequence states.
+ *
+ * @param s
+ * @return the stripped string
+ */
+ String strip(String s) {
+ return strip(s, sub.getAlphabet());
+ }
+
+ static String strip(String s, String residues) {
+ return strip(s, residues, false);
+ }
+
+ static String strip(String s, String residues, boolean allowGaps) {
+
+ boolean[] valid = new boolean[127];
+ for (int i = 0; i < residues.length(); i++) {
+ char c = residues.charAt(i);
+ if (c>='A' && c<='Z') {
+ valid[c] = valid[c + 32] = true;
+ } else if (c>='a' && c<='z') {
+ valid[c - 32] = valid[c] = true;
+ }
+ else {
+ valid[c]=true;
+ }
+ }
+ if (allowGaps) {
+ valid['-'] = true;
+ }
+ StringBuilder res = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ if (valid[s.charAt(i)]) {
+ res.append(s.charAt(i));
+ }
+ }
+ //System.out.println("from =" +s);
+ //System.out.println("to =" +res.toString());
+
+ return res.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignAffine.java b/src/jebl/evolution/align/AlignAffine.java
new file mode 100644
index 0000000..a3ec162
--- /dev/null
+++ b/src/jebl/evolution/align/AlignAffine.java
@@ -0,0 +1,117 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+
+/**
+ * Alignment with affine gap costs
+ */
+
+abstract class AlignAffine extends Align {
+
+ float e; // gap extension cost
+ float[][][] F = null; // the matrices used to compute the alignment
+ TracebackAffine[][][] B = null; // the traceback matrix
+ private int oldn = 0;
+ private int oldm = 0;
+
+ public AlignAffine(Scores sub, float openGapPenalty, float extendGapPenalty) {
+ super(sub, openGapPenalty);
+ setGapExtend(extendGapPenalty);
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+
+ n = sq1.length();
+ m = sq2.length();
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+
+ //first time running this alignment. Create all new matrices.
+ if(F == null) {
+ F = new float[3][n+1][m+1];
+ B = new TracebackAffine[3][n+1][m+1];
+ for(int k = 0; k < 3; k++) {
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[k][i][j] = new TracebackAffine(0,0,0);
+ }
+ }
+ oldn = n;
+ oldm = m;
+ }
+
+ //alignment already been run but matrices not big enough for new alignment.
+ //create all new matrices.
+ else if(sq1.length() > oldn || sq2.length() > oldm) {
+ int extram = 5;
+ int extran = 5;
+// System.out.println ("creating new arrays "+n+ "," +m+ " was " + oldn+ "," + oldm);
+ F = new float[3][n+1+extran][m+1+extram];
+ B = new TracebackAffine[3][n+1+extran][m+1+extram];
+ for(int k = 0; k < 3; k++) {
+ for(int i = 0; i < n+1+extran; i ++) {
+ for(int j = 0; j < m+1+extram;j++)
+ B[k][i][j] = new TracebackAffine(0,0,0);
+ }
+ }
+ oldn = n + extran;
+ oldm = m + extram;
+ }
+ }
+
+ public void setGapExtend(float e) {
+ this.e = e;
+ }
+
+ /**
+ * Get the next state in the traceback
+ *
+ * @param tb current Traceback
+ * @return next Traceback
+ */
+ public Traceback next(Traceback tb) {
+ TracebackAffine tb3 = (TracebackAffine)tb;
+
+ //traceback has reached the origin, therefore stop.
+ if(tb3.i + tb3.j + B[tb3.k][tb3.i][tb3.j].i + B[tb3.k][tb3.i][tb3.j].j == 0)
+ return null;
+
+ else
+ return B[tb3.k][tb3.i][tb3.j];
+ }
+
+ /**
+ * @return score for this alignment
+ */
+ public float getScore() {
+ return F[((TracebackAffine)B0).k][B0.i][B0.j];
+ }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+
+ for (int k=0; k<3; k++) {
+ out.println("F[" + k + "]:");
+ for (int j=0; j<=m; j++) {
+ for (int i=0; i<F[k].length; i++) {
+ out.print(padLeft(formatScore(F[k][i][j]), 5));
+ }
+ out.println();
+ }
+ }
+ }
+}
+
diff --git a/src/jebl/evolution/align/AlignCommand.java b/src/jebl/evolution/align/AlignCommand.java
new file mode 100644
index 0000000..57bda7d
--- /dev/null
+++ b/src/jebl/evolution/align/AlignCommand.java
@@ -0,0 +1,453 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+import jebl.evolution.align.scores.ScoresFactory;
+
+import java.io.*;
+
+/**
+ * A command line interface for the algorithms in jebl.evolution.align.
+ * Imports from FASTA files.
+ *
+ * @author Richard Moir
+ *
+ * @version $Id: AlignCommand.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ */
+public class AlignCommand {
+
+ private boolean relaunch = true; //run command interface again
+ private static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+
+ public AlignCommand() {
+ System.out.println("Usage: AlignCommand <sequence1 file name> <sequence2 file name>\n(FASTA format files)");
+ }
+
+ public AlignCommand(String sq1File, String sq2File) {
+
+ //import sequences from files.
+ String sq1 = "";
+ String sq2 = "";
+ try {
+ BufferedReader br1 = new BufferedReader(new FileReader(sq1File));
+ BufferedReader br2 = new BufferedReader(new FileReader(sq2File));
+
+ String line = br1.readLine();
+ if(!((line.substring(0,1)).equals(">")))
+ sq1 += line;
+ line = br1.readLine();
+ while(line != null) {
+ if(line.substring(0,1).equals(">")) break;
+ sq1 = sq1.concat(line.trim());
+ line = br1.readLine();
+ }
+
+ line = br2.readLine();
+ if(!((line.substring(0,1)).equals(">")))
+ sq2 += line;
+ line = br2.readLine();
+ while(line != null) {
+ if(line.substring(0,1).equals(">")) break;
+ sq2 = sq2.concat(line.trim());
+ line = br2.readLine();
+ }
+
+ }
+ catch(Exception e) {
+ System.out.println("error reading from sequence file\n" + e);
+ }
+
+ while(relaunch)
+ command(sq1, sq2);
+ }
+
+ private void command(String sq1, String sq2) {
+
+ //choose algorithm
+ System.out.println( "Choose algorithm:");
+ System.out.println( "1.NeedlemanWunsch");
+ System.out.println( "2.NeedlemanWunschAffine");
+ System.out.println( "3.NeedlemanWunschLinearSpace");
+ System.out.println( "4.OverlapAlign");
+ System.out.println( "5.RepeatAlign");
+ System.out.println( "6.SmithWaterman");
+ System.out.println( "7.SmithWatermanLinearSpace");
+ System.out.println( "8.SmithWatermanLinearSpaceAffine");
+ System.out.println( "9.NonOverlapMultipleAlign");
+ System.out.println("10.NeedlemanWunschLinearSpaceAffine");
+
+ int input = 0;
+ try {
+ input = Integer.parseInt(readInput("? "));
+ if(input < 1 || input > 10) throw new Exception("must be 1 to 10");
+ }
+ catch(Exception e) {
+ System.out.println("invalid entry\n" + e);
+ System.exit(-1);
+ }
+
+ //construct the alignment
+ AlignSimple as = null;
+ AlignAffine aa = null;
+ AlignRepeat ar = null;
+ AlignRepeatAffine ara = null;
+
+ switch(input) {
+ case 1:
+ as = new NeedlemanWunsch(null, 0);
+ break;
+ case 2:
+ aa = new NeedlemanWunschAffine(null, 0, 0);
+ break;
+ case 3:
+ as = new NeedlemanWunschLinearSpace(null, 0);
+ break;
+ case 4:
+ as = new OverlapAlign(null, 0);
+ break;
+ //case 5:
+ // ar = new RepeatAlign(null, 0, 0);
+ // break;
+ case 6:
+ as = new SmithWaterman(null, 0);
+ break;
+ case 7:
+ as = new SmithWatermanLinearSpace(null, 0);
+ break;
+ case 8:
+ aa = new SmithWatermanLinearSpaceAffine(null, 0, 0);
+ break;
+ case 9:
+ ara = new NonOverlapMultipleLocalAffine(null, 0, 0, 0);
+ break;
+ case 10:
+ aa = new NeedlemanWunschLinearSpaceAffine(null, 0, 0);
+ break;
+ }
+
+ //choose alignment parameters
+ System.out.println("Enter gap open penalty:");
+ float gapOpen = 0;
+ try {
+ gapOpen = Float.parseFloat(readInput("? "));
+ }
+ catch(Exception e) {
+ System.out.println("invalid entry\n" + e);
+ System.exit(-1);
+ }
+
+ float gapExtend = 0;
+ if(aa != null || ara != null) {
+ System.out.println("Enter gap extend penalty:");
+ try {
+ gapExtend = Float.parseFloat(readInput("? "));
+ }
+ catch(Exception e) {
+ System.out.println("invalid entry\n" + e);
+ System.exit(-1);
+ }
+ }
+
+ int threshold = 0;
+ if(ar != null || ara != null) {
+ System.out.println("Enter threshold T:");
+ try {
+ threshold = Integer.parseInt(readInput("? "));
+ }
+ catch(Exception e) {
+ System.out.println("invalid entry\n" + e);
+ System.exit(-1);
+ }
+ }
+
+ //choose substitution matrix to use
+ System.out.println("Enter substitution matrix type(1.BLOSUM, 2.PAM, 3.JUKESCANTOR):");
+ String subType = null;
+ String subVal = null;
+ float dist = 0; //for calculating nucleotide substitution matrix.
+
+ try {
+ subType = readInput("? ");
+ if(subType.equals("BLOSUM") || subType.equals("1")) {
+ System.out.println("Enter BLOSUM value(45 - 90):");
+ subVal = readInput("? ");
+ subType = "Blosum";
+ }
+ else if(subType.equals("PAM") || subType.equals("2")) {
+ System.out.println("Enter PAM value(100 - 250):");
+ subVal = readInput("? ");
+ subType = "Pam";
+ }
+ else if(subType.equals("JUKESCANTOR") || subType.equals("3")) {
+ System.out.println("Enter evolutionary distance d:");
+ dist = Float.parseFloat(readInput("? "));
+ subType = "JukesCantor";
+ }
+ else throw new Exception("must be 1 to 3");
+ }
+ catch(Exception e) {
+ System.out.println("invalid entry\n" + e);
+ System.exit(-1);
+ }
+
+ Scores sub; //substitution matrix
+
+ if(subVal != null) {
+ sub = ScoresFactory.generateScores(subType + subVal); //subType, Integer.parseInt(subVal));
+ }
+ else {
+ sub = ScoresFactory.generateScores(subType + dist); //subType, dist);
+ }
+
+ //choose shuffling
+ boolean shuffle = false;
+ int numShuffles = 0;
+ int numRepeats = 1;
+ try {
+ System.out.println("Do you wish to perform shuffling for the alignment(y/n):");
+ String shuff = readInput("? ");
+ if((shuff.toLowerCase()).equals("y")) {
+ System.out.println("How many times do you wish to shuffle:");
+ numShuffles = Integer.parseInt(readInput("? "));
+ shuffle = true;
+ }
+ else {
+ System.out.println("How many times do you wish to run the alignment:");
+ numRepeats = Integer.parseInt(readInput("? "));
+ if(numRepeats < 1) {
+ System.out.println("Must run atleast once, exiting..");
+ System.exit(-1);
+ }
+ }
+ }
+ catch(Exception e) {
+
+ }
+
+ long start; //time algorithm started
+ long fin; //time algorithm finished
+ String outPut = ""; //string to print to command line and logfile
+
+ String match; //string representation of alignment
+ String algoName; //name of algorithm used
+ float score; //alignment score
+
+ if(!shuffle) { //no shuffling
+ if(as != null) { //simple alignment
+ start = System.currentTimeMillis();
+ for(int i = 0; i < numRepeats; i++) {
+ as.setGapOpen(gapOpen);
+ as.setScores(sub);
+ as.doAlignment(sq1, sq2);
+ }
+ fin = System.currentTimeMillis();
+ match = chopSequence(as.getMatch());
+ algoName = formatAlgoName((as.getClass()).getName());
+ score = as.getScore();
+ }
+ else if(aa != null) { //affine alignment
+ start = System.currentTimeMillis();
+ for(int i = 0; i < numRepeats; i++) {
+ aa.setGapExtend(gapOpen);
+ aa.setGapOpen(gapOpen);
+ aa.setScores(sub);
+ aa.doAlignment(sq1, sq2);
+ }
+ fin = System.currentTimeMillis();
+ match = chopSequence(aa.getMatch());
+ algoName = formatAlgoName((aa.getClass()).getName());
+ score = aa.getScore();
+ }
+ else if(ar != null) { //repeated alignment
+ start = System.currentTimeMillis();
+ for(int i = 0; i < numRepeats; i++) {
+ ar.setGapOpen(gapOpen);
+ ar.setScores(sub);
+ ar.setThreshold(threshold);
+ ar.doAlignment(sq1, sq2);
+ }
+ fin = System.currentTimeMillis();
+ match = chopSequence(ar.getMatch());
+ algoName = formatAlgoName((ar.getClass()).getName());
+ score = ar.getScore();
+ }
+ else { //repeated affine alignment
+ start = System.currentTimeMillis();
+ for(int i = 0; i < numRepeats; i++) {
+ ara.setGapExtend(gapExtend);
+ ara.setGapOpen(gapOpen);
+ ara.setThreshold(threshold);
+ ara.setScores(sub);
+ ara.doAlignment(sq1, sq2);
+ }
+ fin = System.currentTimeMillis();
+ match = chopSequence(ara.getMatch());
+ algoName = formatAlgoName((ara.getClass()).getName());
+ score = ara.getScore();
+ }
+
+ //print output
+ long timeTaken = fin - start;
+ outPut = outPut.concat(numRepeats + " repeat(s) of " + algoName + " took " + timeTaken + "ms.\n\n");
+ if(ara == null)
+ outPut = outPut.concat("Alignment (Score " + score + "):\n\n");
+ else
+ outPut = outPut.concat("Alignment (Total Score " + score + "):\n\n");
+ outPut = outPut.concat(match + "\n" + "\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\\n");
+ System.out.println(outPut);
+ }
+
+ else { //use shuffling
+ SequenceShuffler shuff = new SequenceShuffler();
+ if(as != null) {
+ start = System.currentTimeMillis();
+ as.setGapOpen(gapOpen);
+ as.setScores(sub);
+ shuff.shuffle(as, sq1, sq2, numShuffles);
+ as.doAlignment(sq1, sq2);
+ fin = System.currentTimeMillis();
+ match = chopSequence(as.getMatch());
+ algoName = formatAlgoName((as.getClass()).getName());
+ score = as.getScore();
+ }
+ else if(aa != null) {
+ start = System.currentTimeMillis();
+ aa.setGapExtend(gapOpen);
+ aa.setGapOpen(gapOpen);
+ aa.setScores(sub);
+ shuff.shuffle(aa, sq1, sq2, numShuffles);
+ aa.doAlignment(sq1, sq2);
+ fin = System.currentTimeMillis();
+ match = chopSequence(aa.getMatch());
+ algoName = formatAlgoName((aa.getClass()).getName());
+ score = aa.getScore();
+ }
+ else if(ar != null) {
+ start = System.currentTimeMillis();
+ ar.setGapOpen(gapOpen);
+ ar.setScores(sub);
+ ar.setThreshold(threshold);
+ shuff.shuffle(ar, sq1, sq2, numShuffles);
+ ar.doAlignment(sq1, sq2);
+ fin = System.currentTimeMillis();
+ match = chopSequence(ar.getMatch());
+ algoName = formatAlgoName((ar.getClass()).getName());
+ score = ar.getScore();
+ }
+ else { //repeated affine alignment
+ start = System.currentTimeMillis();
+ ara.setGapExtend(gapExtend);
+ ara.setGapOpen(gapOpen);
+ ara.setThreshold(threshold);
+ ara.setScores(sub);
+ shuff.shuffle(ara, sq1, sq2, numShuffles);
+ ara.doAlignment(sq1, sq2);
+ fin = System.currentTimeMillis();
+ match = chopSequence(ara.getMatch());
+ algoName = formatAlgoName((ara.getClass()).getName());
+ score = ara.getScore();
+ }
+
+ //print output
+ long timeTaken = fin - start;
+ outPut = outPut.concat(numShuffles + " shuffle(s) of " + algoName + " took " + timeTaken + "ms.\n\n");
+ if(ara == null)
+ outPut = outPut.concat("Alignment (Score " + score + "):\n\n");
+ else
+ outPut = outPut.concat("Alignment (Total Score " + score + "):\n\n");
+ outPut = outPut.concat(match + "\n");
+ outPut = outPut.concat("Shuffling " + numShuffles + " times:\tMean: " + shuff.getMean() + "\tstdev: " + shuff.getStdev() + "\n\n\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\\n");
+ System.out.println(outPut);
+ }
+
+ //print output to logfile
+ try {
+ PrintWriter pw = new PrintWriter(new FileWriter("alignment_log.txt", true));
+ pw.print(outPut + "\n\n");
+ pw.close();
+ System.out.println("result logged in alignment_log.txt.\n");
+ }
+ catch(Exception e) {
+ System.out.println("error writing logfile\n" + e);
+ }
+
+ //choose to relaunch command
+ try {
+ relaunch = false;
+ System.out.println("Do you wish to perform another alignment with these sequences(y/n):");
+ String another = readInput("? ");
+ if((another.toLowerCase()).equals("y")) {
+ relaunch = true;
+ }
+ }
+ catch(Exception e) {}
+ }
+
+ /**
+ * Reads input from keyboard.
+ *
+ * @param s mesage printed before input
+ * @return keyboard input
+ */
+ private String readInput(String s) {
+ System.out.print(s);
+ try {
+ return in.readLine();
+ }
+ catch(IOException e) {
+ System.out.println("error reading from keyboard!\n" + e);
+ }
+ return null;
+ }
+
+ /**
+ * @param algoName
+ * @return substring of algoName from after index of the last '.'
+ */
+ private String formatAlgoName(String algoName) {
+ String currentChar = "";
+ int cc = algoName.length();
+ while(!currentChar.equals(".") && cc >= 0) {
+ currentChar = algoName.substring(cc - 1, cc);
+ cc --;
+ }
+ return algoName.substring(cc + 1);
+ }
+
+ /**
+ * Chops the two sequences into 100 character lengths and puts them on new lines.
+ * The two sequences must be the same length (aligned in some way).
+ *
+ * @param sq array of aligned sequences (from method getMatch()).
+ * @return chopped sequences
+ */
+ private String chopSequence(String[] sq) {
+ if(sq[0].length() != sq[1].length()) {
+ return "error! cannot chop sequences of different lengths with this method";
+ }
+ String s = "";
+ int size = sq[1].length();
+ for(int i = 100; i < size + 100; i += 100) {
+ if(i > size) {
+ s = s.concat(sq[0].substring(i - 100, size) + "\n");
+ s = s.concat(sq[1].substring(i - 100, size) + "\n\n");
+ }
+ else {
+ s = s.concat(sq[0].substring(i - 100, i) + "\n");
+ s = s.concat(sq[1].substring(i - 100, i) + "\n\n");
+ }
+ }
+
+ return s;
+ }
+
+ public static void main(String args[]) {
+ try {
+ new AlignCommand(args[0], args[1]);
+ }
+ catch(Exception e) {
+ System.out.println(e);
+ new AlignCommand();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignLinearSpace.java b/src/jebl/evolution/align/AlignLinearSpace.java
new file mode 100644
index 0000000..e31891c
--- /dev/null
+++ b/src/jebl/evolution/align/AlignLinearSpace.java
@@ -0,0 +1,45 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+abstract class AlignLinearSpace extends AlignSimple {
+
+ float[][] F = null; // the matrices used to compute the alignment
+
+ public AlignLinearSpace(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param seq1
+ * @param seq2
+ */
+ public abstract void doAlignment(String seq1, String seq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ F = new float[2][m+1];
+ }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+ for (int j=0; j<=m; j++) {
+ for (int i = 0; i < F.length; i++) {
+ float[] f = F[i];
+ out.print(padLeft(formatScore(f[j]), 5));
+ }
+ out.println();
+ }
+ }
+
+ static void swap01(Object[] A) {
+ Object tmp = A[1]; A[1] = A[0]; A[0] = tmp;
+ }
+}
diff --git a/src/jebl/evolution/align/AlignLinearSpaceAffine.java b/src/jebl/evolution/align/AlignLinearSpaceAffine.java
new file mode 100644
index 0000000..637711c
--- /dev/null
+++ b/src/jebl/evolution/align/AlignLinearSpaceAffine.java
@@ -0,0 +1,48 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+// Alignment with affine gap costs; smart linear-space algorithm
+
+abstract class AlignLinearSpaceAffine extends AlignAffine {
+
+ float[][][] F; // the matrices used to compute the alignment
+
+ public AlignLinearSpaceAffine(Scores sub, float openGapPenalty, float extendGapPenalty) {
+ super(sub, openGapPenalty, extendGapPenalty);
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+ F = new float[3][2][m+1];
+ }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+ for (int k=0; k<3; k++) {
+ out.println("F[" + k + "]:");
+ for (int j=0; j<=m; j++) {
+ for (int i=0; i<F[k].length; i++)
+ out.print(padLeft(formatScore(F[k][i][j]), 5));
+ out.println();
+ }
+ }
+ }
+
+ static void swap01(Object[] A)
+ { Object tmp = A[1]; A[1] = A[0]; A[0] = tmp; }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignRepeat.java b/src/jebl/evolution/align/AlignRepeat.java
new file mode 100644
index 0000000..922feba
--- /dev/null
+++ b/src/jebl/evolution/align/AlignRepeat.java
@@ -0,0 +1,89 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+abstract class AlignRepeat extends Align {
+
+ float[][] F; // the matrix used to compute the alignment
+ TracebackSimple[][] B; // the traceback matrix
+ int T; // threshold
+
+ public AlignRepeat(Scores sub, float d, int T) {
+ super(sub, d);
+ this.T = T;
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+
+ //first time running this alignment. Create all new matrices.
+ if(F == null) {
+ F = new float[n+1][m+1];
+ B = new TracebackSimple[n+1][m+1];
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[i][j] = new TracebackSimple(0,0);
+ }
+ }
+
+ //alignment already been run but matrices not big enough for new alignment.
+ //create all new matrices.
+ else if(sq1.length() > n || sq2.length() > m) {
+ F = new float[n+1][m+1];
+ B = new TracebackSimple[n+1][m+1];
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[i][j] = new TracebackSimple(0,0);
+ }
+ }
+ }
+
+ public void setThreshold(int T) {
+ this.T = T;
+ }
+
+ /**
+ * Get the next state in the traceback
+ *
+ * @param tb current Traceback
+ * @return next Traceback
+ */
+ public Traceback next(Traceback tb) {
+ TracebackSimple tb2 = (TracebackSimple)tb;
+ if(tb.i == 0 && tb.j == 0 && B[tb2.i][tb2.j].i == 0 && B[tb2.i][tb2.j].j == 0)
+ return null;
+ else
+ return B[tb2.i][tb2.j];
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return F[B0.i][B0.j]; }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+
+ for (int j=0; j<=m; j++) {
+ for (float[] f : F) {
+ out.print(padLeft(formatScore(f[j]), 5));
+ }
+ out.println();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignRepeatAffine.java b/src/jebl/evolution/align/AlignRepeatAffine.java
new file mode 100644
index 0000000..3bb64a3
--- /dev/null
+++ b/src/jebl/evolution/align/AlignRepeatAffine.java
@@ -0,0 +1,104 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+abstract class AlignRepeatAffine extends AlignRepeat {
+
+ float[][][] F = null; // the matrix used to compute the alignment
+ TracebackAffine[][][] B; // the traceback matrix
+ float maxScore;
+ float e;
+
+
+ public AlignRepeatAffine(Scores sub, float d, float e, int T) {
+ super(sub, d, T);
+ setGapExtend(e);
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+
+ //first time running this alignment. Create all new matrices.
+ if(F == null) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ F = new float[3][n+1][m+1];
+ B = new TracebackAffine[3][n+1][m+1];
+ for(int k = 0; k < 3; k++) {
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[k][i][j] = new TracebackAffine(0,0,0);
+ }
+ }
+ }
+
+ //alignment already been run and existing matrix is big enough to reuse.
+ else if(seq1.length() <= n && seq2.length() <= m) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ }
+
+ //alignment already been run but matrices not big enough for new alignment.
+ //create all new matrices.
+ else {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ F = new float[3][n+1][m+1];
+ B = new TracebackAffine[3][n+1][m+1];
+ for(int k = 0; k < 3; k++) {
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[k][i][j] = new TracebackAffine(0,0,0);
+ }
+ }
+ }
+ }
+
+ public void setGapExtend(float e) {
+ this.e = e;
+ }
+
+ /**
+ * Get the next state in the traceback
+ *
+ * @param tb current Traceback
+ * @return next Traceback
+ */
+ public Traceback next(Traceback tb) {
+ TracebackAffine tb3 = (TracebackAffine)tb;
+ if(tb3.i + tb3.j + B[tb3.k][tb3.i][tb3.j].i + B[tb3.k][tb3.i][tb3.j].j == 0)
+ return null; //traceback has reached origin therefore stop.
+ else
+ return B[tb3.k][tb3.i][tb3.j];
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return F[((TracebackAffine)B0).k][B0.i][B0.j]; }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+
+ for (int k=0; k<3; k++) {
+ out.println("F[" + k + "]:");
+ for (int j=0; j<=m; j++) {
+ for (int i=0; i<F[k].length; i++) {
+ out.print(padLeft(formatScore(F[k][i][j]), 5));
+ }
+ out.println();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignSimple.java b/src/jebl/evolution/align/AlignSimple.java
new file mode 100644
index 0000000..964aaa7
--- /dev/null
+++ b/src/jebl/evolution/align/AlignSimple.java
@@ -0,0 +1,90 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+abstract class AlignSimple extends Align {
+
+ float[][] F = null; // the matrix used to compute the alignment
+ TracebackSimple[][] B; // the traceback matrix
+
+ public AlignSimple(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * Performs the alignment. Abstract.
+ *
+ * @param sq1
+ * @param sq2
+ */
+ public abstract void doAlignment(String sq1, String sq2);
+
+ public void prepareAlignment(String sq1, String sq2) {
+
+ //first time running this alignment. Create all new matrices.
+ if(F == null) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ F = new float[n+1][m+1];
+ B = new TracebackSimple[n+1][m+1];
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[i][j] = new TracebackSimple(0,0);
+ }
+ }
+
+ //alignment already been run and existing matrix is big enough to reuse.
+ else if(seq1.length() <= this.n && seq2.length() <= this.m) {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ }
+
+ //alignment already been run but matrices not big enough for new alignment.
+ //create all new matrices.
+ else {
+ this.n = sq1.length(); this.m = sq2.length();
+ this.seq1 = strip(sq1); this.seq2 = strip(sq2);
+ F = new float[n+1][m+1];
+ B = new TracebackSimple[n+1][m+1];
+ for(int i = 0; i < n+1; i ++) {
+ for(int j = 0; j < m+1; j++)
+ B[i][j] = new TracebackSimple(0,0);
+ }
+ }
+ }
+
+ /**
+ * Get the next state in the traceback
+ *
+ * @param tb current Traceback
+ * @return next Traceback
+ */
+ public Traceback next(Traceback tb) {
+ TracebackSimple tb2 = (TracebackSimple)tb;
+ if((tb.i == 0 && tb.j == 0 && B[tb2.i][tb2.j].i == 0 && B[tb2.i][tb2.j].j == 0) || tb.i == -1)
+ return null;
+ else
+ return B[tb2.i][tb2.j];
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return F[B0.i][B0.j]; }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+
+ for (int j=0; j<=m; j++) {
+ for (int i = 0; i < F.length; i++) {
+ float[] f = F[i];
+ out.print(padLeft(formatScore(f[j]), 5));
+ }
+ out.println();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/AlignmentResult.java b/src/jebl/evolution/align/AlignmentResult.java
new file mode 100644
index 0000000..9658096
--- /dev/null
+++ b/src/jebl/evolution/align/AlignmentResult.java
@@ -0,0 +1,41 @@
+package jebl.evolution.align;
+
+/**
+ * @author Matt Kearse
+ * @version $Id: AlignmentResult.java 650 2007-03-12 20:09:10Z twobeers $
+ *
+ * Used for representing the results of a sequence alignment. Basically just stores
+ * an array representing whether or not each position in the alignment is a gap or not.
+ */
+class AlignmentResult {
+ int size;
+ boolean values[];
+ // true represents a character from the original sequence, and false represents a gap
+ // for example:
+ // original sequence: abcd
+ // values: true, false, false, true, true, false, true
+ // then the resulting alignment is: a--bc-d
+
+ public AlignmentResult(int size) {
+ this.size = 0;
+ // TT: Wouldn't a BitSet be better?
+ values =new boolean[size];
+ }
+
+ public void append(String result) {
+ for (char character : result.toCharArray()) {
+ if(character =='-')
+ values [ size ++ ]= false;
+ else
+ values [ size ++ ] = true;
+ }
+ }
+
+ public void print() {
+ for (int i = 0; i < size; i++) {
+ System.out.print(values[i] ? "X" : "-");
+
+ }
+ System.out.println ();
+ }
+}
diff --git a/src/jebl/evolution/align/AlignmentTreeBuilderFactory.java b/src/jebl/evolution/align/AlignmentTreeBuilderFactory.java
new file mode 100644
index 0000000..a72684f
--- /dev/null
+++ b/src/jebl/evolution/align/AlignmentTreeBuilderFactory.java
@@ -0,0 +1,179 @@
+package jebl.evolution.align;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.distances.*;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.trees.SimpleRootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.TreeBuilder;
+import jebl.evolution.trees.TreeBuilderFactory;
+import jebl.util.ProgressListener;
+import jebl.util.CompositeProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * @author Joseph Heled
+ * @version $Id: AlignmentTreeBuilderFactory.java 662 2007-03-21 00:32:24Z twobeers $
+ */
+public class AlignmentTreeBuilderFactory {
+ private final static Logger logger = Logger.getLogger(AlignmentTreeBuilderFactory.class.getName());
+
+ private static interface DistanceMatrixBuilder {
+ DistanceMatrix buildDistanceMatrix(final ProgressListener progressListener);
+ }
+
+ static public class Result {
+ public final Tree tree;
+ public final DistanceMatrix distance;
+
+ Result(Tree tree, DistanceMatrix distance) {
+ this.tree = tree;
+ this.distance = distance;
+ }
+ }
+
+ /**
+ * private utility method to get rid of former code duplication in the two build methods from
+ * alignment and list of sequences with pairwise aligner.
+ * @param distanceMatrixBuilder Encapsulation of all information required to build distance matrix
+ * @param method the tree building method to use
+ * @param _progressListener must not be null
+ * @return A tree building result (containing a tree and a distance matrix)
+ */
+ private static Result build(DistanceMatrixBuilder distanceMatrixBuilder, TreeBuilderFactory.Method method, ProgressListener _progressListener) {
+ // The requirement that progress isn't null has only been added on 2006-12-29.
+ // For a grace period, we check whether it is null and only warn.
+ if (_progressListener == null) {
+ logger.warning("ProgressListener is null");
+ _progressListener = ProgressListener.EMPTY;
+ }
+ CompositeProgressListener progressListener = new CompositeProgressListener(_progressListener, new double[] { .5, .5 });
+
+ progressListener.beginSubtask("Computing genetic distance for all pairs");
+ long start = System.currentTimeMillis();
+ DistanceMatrix distanceMatrix = distanceMatrixBuilder.buildDistanceMatrix(progressListener);
+ logger.fine("took " +(System.currentTimeMillis() - start) + " to build distance matrix");
+ progressListener.beginSubtask("Building tree");
+ TreeBuilder treeBuilder = TreeBuilderFactory.getBuilder(method, distanceMatrix);
+ treeBuilder.addProgressListener(progressListener);
+ Result result = new Result(treeBuilder.build(), distanceMatrix);
+ treeBuilder.removeProgressListener(progressListener);
+ return result;
+ }
+
+ /**
+ * @param alignment Alignment to calculate distance matrix from
+ * @param method the tree building method to use
+ * @param model substitution model for distance matrix: JukesCantor, TamuraNei, HKY or F84.
+ * @param progressListener must not be null. If you are not interested in progress, pass in ProgressListener.EMPTY
+ * @return A tree building result (containing a tree and a distance matrix)
+ */
+ static public Result build(final Alignment alignment, TreeBuilderFactory.Method method, final TreeBuilderFactory.DistanceModel model, ProgressListener progressListener) {
+ DistanceMatrixBuilder matrixBuilder = new DistanceMatrixBuilder() {
+ public DistanceMatrix buildDistanceMatrix(final ProgressListener progressListener) {
+ switch( model ) {
+ case F84:
+ return new F84DistanceMatrix(alignment, progressListener);
+ case HKY:
+ return new HKYDistanceMatrix(alignment, progressListener);
+ case TamuraNei:
+ return new TamuraNeiDistanceMatrix(alignment, progressListener);
+ case JukesCantor:
+ default:
+ return new JukesCantorDistanceMatrix(alignment, progressListener);
+ }
+ }
+ };
+ return build(matrixBuilder, method, progressListener);
+ }
+
+ /**
+ *
+ * @param seqs Sequences to build distance matrix from
+ * @param method method the tree building method to use
+ * @param aligner pairwise aligner which will be used to calculate a pairwise distance
+ * @param progressListener must not be null. If you are not interested in progress, pass in ProgressListener.EMPTY
+ * @return A tree building result (containing a tree and a distance matrix)
+ */
+ static public Result build(final List<Sequence> seqs, TreeBuilderFactory.Method method, final PairwiseAligner aligner,
+ ProgressListener progressListener) {
+ DistanceMatrixBuilder matrixBuilder = new DistanceMatrixBuilder() {
+ public DistanceMatrix buildDistanceMatrix(final ProgressListener progressListener) {
+ return new SequenceAlignmentsDistanceMatrix(seqs, aligner, progressListener);
+ }
+ };
+ return build(matrixBuilder, method, progressListener);
+ }
+
+ static public Result build(List<Sequence> seqs, TreeBuilderFactory.Method method, MultipleAligner aligner,
+ /*boolean needDistances, */ProgressListener progress) {
+ // needDistances = false;
+
+ SimpleRootedTree gtree = new SimpleRootedTree();
+ List<Node> nodes = new ArrayList<Node>();
+ for(Sequence s : seqs) {
+ Node tip = gtree.createExternalNode(s.getTaxon());
+ nodes.add(tip);
+ }
+
+ int nnodes = nodes.size();
+ while( nnodes > 1 ) {
+ List<Node> upnodes = new ArrayList<Node>();
+ for(int k = 0; k < nnodes/2; ++k) {
+ upnodes.add(gtree.createInternalNode(nodes.subList(2*k,2*k+2)));
+ }
+ if( (nnodes & 1) != 0 ) {
+ upnodes.add(nodes.get(nnodes - 1));
+ }
+ nodes = upnodes;
+ nnodes = nodes.size();
+ }
+
+ final int alignWork = seqs.size()-1;
+ final int treeWork = 1;
+ final int matrixWork = 0; // needDistances ? 1 : 0;
+
+ CompoundAlignmentProgressListener p = new CompoundAlignmentProgressListener(progress,
+ alignWork + treeWork + matrixWork);
+ final ProgressListener minorProgress = p.getMinorProgress();
+
+ progress.setMessage("Building alignment for guide");
+ p.setSectionSize(alignWork);
+ final Alignment alignment = aligner.doAlign(seqs, gtree, minorProgress);
+ if (p.isCanceled()) {
+ return null;
+ }
+ p.incrementSectionsCompleted(alignWork);
+
+ final boolean isProtein = seqs.get(0).getSequenceType().getCanonicalStateCount() > 4;
+
+ final TreeBuilderFactory.DistanceModel distanceModel =
+ isProtein ? TreeBuilderFactory.DistanceModel.JukesCantor : TreeBuilderFactory.DistanceModel.HKY;
+
+ p.setSectionSize(treeWork);
+ progress.setMessage("Building guide tree from alignment");
+ final Result result = build(alignment, method, distanceModel, minorProgress);
+ //final Tree guideTree = result.tree;
+ p.incrementSectionsCompleted(treeWork);
+ return result;
+ /*
+ DistanceMatrix distanceMatrix = null;
+ if( needDistances ) {
+ p.setSectionSize(matrixWork);
+ progress.setMessage("Computing genetic distance for all pairs");
+
+ if( distanceModel == TreeBuilderFactory.DistanceModel.HKY ) {
+ distanceMatrix = new HKYDistanceMatrix(alignment, minorProgress);
+ } else {
+ distanceMatrix = new JukesCantorDistanceMatrix(alignment, minorProgress);
+ }
+ p.incrementSectionsCompleted(matrixWork);
+ }
+ return new Result(guideTree, distanceMatrix);
+ */
+ }
+}
diff --git a/src/jebl/evolution/align/BartonSternberg.java b/src/jebl/evolution/align/BartonSternberg.java
new file mode 100644
index 0000000..3812f6f
--- /dev/null
+++ b/src/jebl/evolution/align/BartonSternberg.java
@@ -0,0 +1,364 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Blosum60;
+import jebl.evolution.align.scores.NucleotideScores;
+import jebl.evolution.align.scores.Scores;
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.BasicAlignment;
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.io.FastaImporter;
+import jebl.evolution.io.ImportException;
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.TreeBuilderFactory;
+import jebl.evolution.trees.Utils;
+import jebl.util.ProgressListener;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Matt Kearse
+ * @version $Id: BartonSternberg.java 645 2007-03-02 01:29:18Z richardmoir $
+ *
+ * Implements the BartonSternberg multiple sequence alignment algorithm.
+ *
+ * Note: this is not yet complete, it does not create an initial ordering
+ * in which to add sequences to the profile.
+ *
+ * Also, after creating the profile, it just removes and adds each sequence back into
+ * the profile a fixed number of times(currently two).
+ */
+public class BartonSternberg implements MultipleAligner {
+
+ Scores scores;
+ NeedlemanWunschLinearSpaceAffine aligner;
+ private int refinementIterations;
+ private float gapOpen,gapExtend;
+ private boolean freeGapsAtEnds;
+ private boolean fastGuide;
+ // if not null, scores are from estimate
+ private Scores origScores = null;
+
+ private void establishScores(Scores scores) {
+ this.scores = scores;
+ this.scores = Scores.includeGaps(scores, -gapExtend, 0);
+ aligner = new NeedlemanWunschLinearSpaceAffine(this.scores, gapOpen, gapExtend, freeGapsAtEnds);
+ }
+
+ public Scores getEstimatedScores() {
+ return origScores != null ? scores : null;
+ }
+
+ public BartonSternberg(Scores scores, float gapOpen, float gapExtend, int refinementIterations,
+ boolean freeGapsAtEnds, boolean fastGuide) {
+// if (true) throw new RuntimeException("testing");
+ this.gapOpen = gapOpen;
+ this.gapExtend = gapExtend;
+ this.freeGapsAtEnds = freeGapsAtEnds;
+
+ this.fastGuide = fastGuide;
+
+ this.refinementIterations = refinementIterations;
+ establishScores(scores);
+ }
+
+ CompoundAlignmentProgressListener compoundProgress;
+ /*private ProgressListener progress;
+ private boolean cancelled = false;
+ private int sectionsCompleted;
+ private int totalSections;
+ ProgressListener minorProgress = new ProgressListener() {
+ public boolean setProgress(double fractionCompleted) {
+ double totalProgress = (sectionsCompleted + fractionCompleted)/totalSections;
+ if(progress.setProgress(totalProgress)) cancelled = true;
+ return cancelled;
+ }
+ };*/
+
+ // on entry from top (non recursive), compoundProgress should have allocated #tips - 1 utins of work
+
+ private Profile align(RootedTree tree, Node node, List<Sequence> seqs,
+ CompoundAlignmentProgressListener compoundProgress) {
+ if( tree.isExternal(node) ) {
+ final Taxon tax = tree.getTaxon(node);
+ final int iSeq = Integer.parseInt(tax.getName());
+
+ final Profile profile = new Profile(scores.getAlphabet().length());
+ profile.addSequence(iSeq, seqs.get(iSeq).getString());
+ return profile;
+ }
+
+ List<Node> children = tree.getChildren(node); assert( children.size() == 2 );
+ final Profile left = align(tree, children.get(0), seqs, compoundProgress);
+ if( compoundProgress.isCanceled() ) return null;
+
+ final Profile right = align(tree, children.get(1), seqs, compoundProgress);
+ if( compoundProgress.isCanceled() ) return null;
+
+ compoundProgress.setSectionSize(1);
+ AlignmentResult results[] = aligner.doAlignment(left, right, compoundProgress.getMinorProgress(), false);
+ compoundProgress.incrementSectionsCompleted(1);
+ if(compoundProgress.isCanceled()) return null;
+ return Profile.combine(left, right, results[0], results[1]);
+ }
+
+
+ /**
+ *
+ * @param sourceSequences
+ * @param progress
+ * @param refineOnly if specified, then the input sequences are assumed to be aligned already,
+ * and this function will only refine the alignment.
+ */
+ public String[] align(List<Sequence> sourceSequences, ProgressListener progress, boolean refineOnly,
+ boolean estimateMatchMismatchCosts) {
+ if( origScores != null ) {
+ establishScores(origScores);
+ }
+
+ final int count = sourceSequences.size();
+
+ Profile[] sequenceProfilesWithoutGaps = new Profile[count];
+ String[] sequencesWithoutGaps = new String[count];
+ for (int i = 0; i < count; i++) {
+ sequencesWithoutGaps[i] = Align.strip(sourceSequences.get(i).getString(), scores.getAlphabet(), false);
+ sequenceProfilesWithoutGaps[i] = new Profile(i, sequencesWithoutGaps[i]);
+ }
+
+ int treeWork = refineOnly ? 0 : (fastGuide ? count : count*(count - 1)/2);
+ int alignmentWork = refineOnly ? 0 : count - 1;
+ int refinementWork = count * refinementIterations;
+
+ compoundProgress = new CompoundAlignmentProgressListener(progress,treeWork + refinementWork + alignmentWork);
+
+ Profile profile = null;
+ if( refineOnly ) {
+ String[] sequencesWithGaps = new String[count];
+ for (int i = 0; i < count; i++) {
+ sequencesWithGaps[i] = Align.strip(sourceSequences.get(i).getString(), scores.getAlphabet(), true);
+
+ }
+ profile = new Profile(Profile.calculateAlphabetSize(sequencesWithGaps));
+ for (int i = 0; i < count; i++) {
+ assert(sequencesWithGaps[i].length() == sequencesWithGaps [0].length ());
+ profile.addSequence(i, sequencesWithGaps[i]);
+ }
+ } else {
+ List<Sequence> sequencesForGuideTree = new ArrayList<Sequence>(sourceSequences.size());
+ for (int i = 0; i < count; i++) {
+ Sequence s = sourceSequences.get(i);
+ sequencesForGuideTree.add(new BasicSequence(s.getSequenceType(), Taxon.getTaxon("" + i), sequencesWithoutGaps[i]));
+ }
+ compoundProgress.setSectionSize(treeWork);
+ // We want a binary rooted tree
+
+ //long start = System.currentTimeMillis();
+ final boolean estimateMatchCost = estimateMatchMismatchCosts && scores instanceof NucleotideScores;
+
+ final AlignmentTreeBuilderFactory.Result unrootedGuideTree =
+ fastGuide ?
+ AlignmentTreeBuilderFactory.build(sequencesForGuideTree, TreeBuilderFactory.Method.NEIGHBOR_JOINING,
+ this, compoundProgress.getMinorProgress()) :
+ AlignmentTreeBuilderFactory.build(sequencesForGuideTree, TreeBuilderFactory.Method.NEIGHBOR_JOINING,
+ aligner, compoundProgress.getMinorProgress());
+ if (compoundProgress.isCanceled()) return null;
+ //long duration = System.currentTimeMillis() - start;
+ //System.out.println("took " + duration + " for " + (fastGuide ? " fast" : "normal") + " guide tree");
+
+ RootedTree guideTree = Utils.rootTreeAtCenter(unrootedGuideTree.tree);
+ compoundProgress.incrementSectionsCompleted(treeWork);
+
+ if( estimateMatchCost ) {
+ final DistanceMatrix distanceMat = unrootedGuideTree.distance;
+ final double[][] distances = distanceMat.getDistances();
+ double sum = 0.0;
+ final int n = distances.length;
+ for(int k = 0; k < n; ++k) {
+ for(int j = k+1; j < n; ++j) {
+ // ignore infinity and high values
+ sum += Math.min(5.0, distances[k][j]);
+ }
+ }
+ final double avg = sum / ((n * (n - 1)/2));
+
+ final double percentmatches = 1 - (3.0/4.0) * (1 - Math.exp(-4.0 * avg / 3.0));
+
+ origScores = scores;
+ final NucleotideScores nucleotideScores = new NucleotideScores(scores, percentmatches);
+ establishScores(nucleotideScores);
+ }
+ progress.setMessage("Building alignment");
+ profile = align(guideTree, guideTree.getRootNode(), sequencesForGuideTree, compoundProgress);
+ if (compoundProgress.isCanceled()) return null;
+ }
+
+ //now remove a single sequence, and we
+ for (int j = 0; j < refinementIterations; j++) {
+ String message = "Refining alignment";
+ if(refinementIterations> 1) {
+ message = message + " (iteration " +(j+1) + " of " + refinementIterations+ ")";
+ }
+ progress.setMessage(message);
+ for (int i = 0; i < count; ++i) {
+// if(j> 0&& i!= 8) continue;
+// Profile sequenceProfile = sequenceProfiles[i];
+ boolean display = false;
+
+ String sequence = profile.getSequence(i);
+ if(j>= 0 && i== 8) {
+// display = true;
+ }
+ if(display) {
+ System.out.println("remove sequence =" + sequence);
+ profile.print (true);
+ }
+ Profile sequenceProfile = new Profile(i, sequence);
+ profile.remove(sequenceProfile);
+// aligner.setDebug(display);
+
+ AlignmentResult results[] = aligner.doAlignment(profile, sequenceProfilesWithoutGaps[i], compoundProgress.getMinorProgress(), false);
+// aligner.setDebug(false);
+ if (compoundProgress.isCanceled()) return null;
+ compoundProgress.incrementSectionsCompleted(1);
+ if(display){
+ profile.print(false);
+
+ System.out.println("result =" + results[0].size + "," + results[1].size + " from " + profile.length() + "," + sequenceProfile.length());
+ }
+ profile = Profile.combine(profile, sequenceProfilesWithoutGaps[i], results[0], results[1]);
+ if(display) {
+ profile.print(true);
+ }
+ }
+ }
+
+ String[] results = new String[count];
+ for (int i = 0; i < count; i++) {
+ results[i]= profile.getSequence(i);
+ }
+ return results;
+ }
+
+ public static void main(String[] arguments) throws IOException, ImportException {
+ File file = new File(arguments[0]);
+ SequenceType sequenceType = SequenceType.AMINO_ACID;
+
+ FastaImporter importer = new FastaImporter(file, sequenceType);
+ List<Sequence> xsequences = importer.importSequences();
+ List<String> sequenceStrings = new ArrayList<String>();
+ int count = 0;
+ int maximum = 10;
+ for (Sequence sequence : xsequences) {
+ BasicSequence basic = (BasicSequence) sequence;
+ String string = basic.getCleanString();
+ sequenceStrings.add(string);
+ System.out.println(string);
+ if(count++ >= maximum) break;
+ }
+ System.out.println ();
+ count = 0;
+ for (Sequence sequence : xsequences) {
+ BasicSequence basic = (BasicSequence) sequence;
+ String string = basic.getString();
+ System.out.println(string);
+ if (count++ >= maximum) break;
+ }
+ long start = System.currentTimeMillis();
+ BartonSternberg alignment = new BartonSternberg( new Blosum60(), 20, 1, 2, true, false);
+ String[] sequences = sequenceStrings.toArray(new String[0]);
+ System.out.println("aligning " + sequences.length);
+ String results[] = alignment.align(xsequences, null, false, false);
+ for (String result : results) {
+ System.out.println(result);
+ }
+ System.out.println ("took " +(System.currentTimeMillis() - start) + " milliseconds");
+ }
+
+ public Alignment doAlign(List<Sequence> seqs, RootedTree guideTree, ProgressListener progress) {
+ final int count = seqs.size();
+ final CompoundAlignmentProgressListener p = new CompoundAlignmentProgressListener(progress, count - 1);
+
+ Profile profile = align(guideTree, guideTree.getRootNode(), seqs, p);
+ if (p.isCanceled()) {
+ return null;
+ }
+
+ List<Sequence> aSeqs = new ArrayList<Sequence>(count);
+ for (int i = 0; i < count; i++) {
+ String seq = profile.getSequence(i);
+ final Sequence s = seqs.get(i);
+ aSeqs.add(new BasicSequence(s.getSequenceType(), s.getTaxon(), seq));
+ }
+ return new BasicAlignment(aSeqs);
+ }
+
+
+ public Alignment doAlign(Alignment a1, Alignment a2, ProgressListener progress) {
+ List<Sequence> seqs1 = a1.getSequenceList();
+ List<Sequence> seqs2 = a2.getSequenceList();
+
+ final int size1 = seqs1.size();
+ final int size2 = seqs2.size();
+
+ final Profile profile1 = new Profile(a1, scores.getAlphabet().length());
+ final Profile profile2 = new Profile(a2, scores.getAlphabet().length(), size1);
+
+ AlignmentResult results[] = aligner.doAlignment(profile1, profile2, progress, false);
+ if (progress.isCanceled()) {
+ return null;
+ }
+ Profile profile = Profile.combine(profile1, profile2, results[0], results[1]);
+
+ final int count = size1 + size2;
+ List<Sequence> aSeqs = new ArrayList<Sequence>(count);
+ for (int i = 0; i < count; i++) {
+ final String seq = profile.getSequence(i);
+ final Sequence s = (i < size1) ? seqs1.get(i) : seqs2.get(i - size1);
+ aSeqs.add(new BasicSequence(s.getSequenceType(), s.getTaxon(), seq));
+ }
+ return new BasicAlignment(aSeqs);
+ }
+
+ public Alignment doAlign(Alignment alignment, Sequence sequence, ProgressListener progress) {
+
+ for (Sequence seq : alignment.getSequenceList()) {
+ if (seq.getTaxon().getName().equals(sequence.getTaxon().getName())) {
+ throw new IllegalArgumentException("Sequence taxon " + sequence.getTaxon().getName() + " appears in alignment and sequence.");
+ }
+ }
+
+ final Profile aprofile = new Profile(alignment, scores.getAlphabet().length(),1);
+
+ final Profile sprofile = new Profile(scores.getAlphabet().length());
+ sprofile.addSequence(0, sequence.getString());
+
+ AlignmentResult results[] = aligner.doAlignment(aprofile, sprofile, progress, false);
+ Profile profile = Profile.combine(aprofile, sprofile, results[0], results[1]);
+
+ List<Sequence> seqs1 = alignment.getSequenceList();
+ List<Sequence> seqs2 = new ArrayList<Sequence>(); seqs2.add(sequence);
+
+ final int size1 = seqs1.size();
+ final int size2 = seqs2.size();
+
+ final int count = size1 + size2;
+ List<Sequence> aSeqs = new ArrayList<Sequence>(count);
+ for (int i = 0; i < count; i++) {
+ final String seq = profile.getSequence(i);
+ final Sequence s = (i < count-1) ? seqs1.get(i) : seqs2.get(0);
+ aSeqs.add(new BasicSequence(s.getSequenceType(), s.getTaxon(), seq));
+ }
+ return new BasicAlignment(aSeqs);
+ }
+
+ public double getScore() {
+ return aligner.getScore();
+ }
+}
diff --git a/src/jebl/evolution/align/CompoundAlignmentProgressListener.java b/src/jebl/evolution/align/CompoundAlignmentProgressListener.java
new file mode 100644
index 0000000..d232a11
--- /dev/null
+++ b/src/jebl/evolution/align/CompoundAlignmentProgressListener.java
@@ -0,0 +1,58 @@
+package jebl.evolution.align;
+
+import jebl.util.ProgressListener;
+
+/**
+ * @author Matt Kearse
+ * @version $Id: CompoundAlignmentProgressListener.java 524 2006-11-12 21:15:21Z matt_kearse $
+ */
+class CompoundAlignmentProgressListener {
+ private boolean cancelled = false;
+ private int sectionsCompleted = 0;
+ private int totalSections;
+ private final ProgressListener progress;
+ private int sectionSize= 1;
+
+ public CompoundAlignmentProgressListener(ProgressListener progress, int totalSections) {
+ this.totalSections = totalSections;
+ this.progress = progress;
+ }
+
+ public void setSectionSize(int size) {
+ this.sectionSize = size;
+ }
+
+ public void incrementSectionsCompleted(int count) {
+ sectionsCompleted += count;
+ }
+
+ public boolean isCanceled() {
+// return cancelled;
+ return progress.isCanceled();
+ }
+
+ public ProgressListener getMinorProgress() {
+ return minorProgress;
+ }
+
+ private ProgressListener minorProgress = new ProgressListener() {
+ protected void _setProgress(double fractionCompleted) {
+// System.out.println("progress =" + fractionCompleted+ " sections =" + sectionsCompleted+ "/" + totalSections);
+ double totalProgress = (sectionsCompleted + fractionCompleted*sectionSize) / totalSections;
+ // if( totalProgress > 1.0 ) System.out.println(totalProgress);
+ progress.setProgress(totalProgress);
+ }
+
+ protected void _setIndeterminateProgress() {
+ progress.setIndeterminateProgress();
+ }
+
+ protected void _setMessage(String message) {
+ progress.setMessage(message);
+ }
+
+ public boolean isCanceled() {
+ return progress.isCanceled();
+ }
+ };
+}
diff --git a/src/jebl/evolution/align/MaximalSegmentPair.java b/src/jebl/evolution/align/MaximalSegmentPair.java
new file mode 100644
index 0000000..8df5ff3
--- /dev/null
+++ b/src/jebl/evolution/align/MaximalSegmentPair.java
@@ -0,0 +1,56 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: MaximalSegmentPair.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class MaximalSegmentPair extends AlignSimple {
+
+ public MaximalSegmentPair(Scores sub) {
+ super(sub, Integer.MAX_VALUE);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public final void doAlignment(String sq1, String sq2) {
+
+ super.prepareAlignment(sq1, sq2);
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ int maxi = n, maxj = m;
+ float maxval = Float.NEGATIVE_INFINITY;
+ float val;
+ for (int i=1; i<=n; i++) {
+ for (int j=1; j<=m; j++) {
+ float s = score[seq1.charAt(i-1)][seq2.charAt(j-1)];
+ val = max(0.0f, F[i-1][j-1]+s);
+ F[i][j] = val;
+ if (val == 0.0f) {
+ B[i][j].setTraceback(-1,-1);
+ } else {
+ B[i][j].setTraceback(i-1, j-1);
+ }
+ if (val > maxval) {
+ maxval = val;
+ maxi = i; maxj = j;
+ }
+ }
+ }
+ B0 = new TracebackSimple(maxi, maxj);
+ }
+
+ public final Traceback next(Traceback tb) {
+
+ Traceback next = super.next(tb);
+ if (next != null) {
+ if (next.i - tb.i != next.j - tb.j) return null;
+ }
+ return next;
+ }
+}
diff --git a/src/jebl/evolution/align/MultipleAligner.java b/src/jebl/evolution/align/MultipleAligner.java
new file mode 100644
index 0000000..c8f5746
--- /dev/null
+++ b/src/jebl/evolution/align/MultipleAligner.java
@@ -0,0 +1,21 @@
+package jebl.evolution.align;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.trees.RootedTree;
+import jebl.util.ProgressListener;
+
+import java.util.List;
+
+/**
+ * @author Joseph Heled
+ * @version $Id: MultipleAligner.java 482 2006-10-25 06:30:57Z twobeers $
+ * Date: 6/05/2006 Time: 08:08:22
+ */
+public interface MultipleAligner {
+ Alignment doAlign(List<Sequence> seqs, RootedTree guideTree, ProgressListener progress);
+
+ Alignment doAlign(Alignment a1, Alignment a2, ProgressListener progress);
+
+ Alignment doAlign(Alignment alignment, Sequence sequence, ProgressListener progress);
+}
diff --git a/src/jebl/evolution/align/NeedlemanWunsch.java b/src/jebl/evolution/align/NeedlemanWunsch.java
new file mode 100644
index 0000000..5a4f03e
--- /dev/null
+++ b/src/jebl/evolution/align/NeedlemanWunsch.java
@@ -0,0 +1,83 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NeedlemanWunsch extends AlignSimple {
+
+ private int prev = 0;
+ private int curr = 1;
+ private float maxScore = 0;
+
+ public NeedlemanWunsch(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ prepareAlignment(sq1, sq2);
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+
+ F[curr][0] = -d;
+ for (int i=1; i<=n; i++) {
+ B[i][0].setTraceback(i-1, 0);
+ }
+ for (int j=1; j<=m; j++) {
+ F[prev][j] = -d * j;
+ B[0][j].setTraceback(0, j-1);
+ }
+ float s, val;
+ for (int i=1; i<=n; i++) {
+ for (int j=1; j<=m; j++) {
+ s = score[s1[i-1]][s2[j-1]];
+ val = max(F[prev][j-1]+s, F[prev][j]-d, F[curr][j-1]-d);
+ F[curr][j] = val;
+ if (val == F[prev][j-1]+s) {
+ B[i][j].setTraceback(i-1, j-1);
+ } else if (val == F[prev][j]-d) {
+ B[i][j].setTraceback(i-1, j);
+ } else if (val == F[curr][j-1]-d) {
+ B[i][j].setTraceback(i, j-1);
+ } else {
+ throw new Error("Error in Needleman-Wunch pairwise alignment.");
+ }
+ }
+ int temp = prev;
+ prev = curr;
+ curr = temp;
+ F[curr][0] = -d * (i + 1);
+ }
+ B0 = new TracebackSimple(n, m);
+ maxScore = F[curr][m];
+ }
+
+ List tracebackList(int startx, int starty) {
+
+ List<TracebackSimple> tracebacks = new ArrayList<TracebackSimple>();
+
+ Traceback tb = B0;
+ while (tb != null) {
+ tracebacks.add(0, new TracebackSimple(tb.i+startx, tb.j+starty));
+ tb = next(tb);
+ }
+
+ return tracebacks;
+ }
+
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return maxScore; }
+}
diff --git a/src/jebl/evolution/align/NeedlemanWunschAffine.java b/src/jebl/evolution/align/NeedlemanWunschAffine.java
new file mode 100644
index 0000000..1d16ac4
--- /dev/null
+++ b/src/jebl/evolution/align/NeedlemanWunschAffine.java
@@ -0,0 +1,329 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+// Global alignment using the Needleman-Wunsch algorithm (affine gap costs)
+
+public class NeedlemanWunschAffine extends AlignAffine {
+ private boolean invert;
+
+ public NeedlemanWunschAffine(Scores sub, float d, float e) {
+ super(sub, d, e);
+ }
+
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+
+ doAlignment(sq1, sq2, TYPE_ANY, TYPE_ANY);
+ //the type_... parameters should generally only be used by the
+ //NeedlemanWunschLinearSpaceAffine algorithm to handle it's base
+ //recursion case.
+ }
+
+ int[][][] Bi, Bj, Bk;
+ private int allocatedn = -1;
+ private int allocatedm = -1;
+ int B0k, B0i, B0j;
+
+ public void allocateMatrices(int n, int m) {
+ //first time running this alignment. Create all new matrices.
+ if (n > allocatedn || m > allocatedm) {
+ n = maxi(n, allocatedn + 5);
+ m = maxi(m, allocatedm + 5);
+ allocatedn = n;
+ allocatedm = m;
+ F = new float[3][n + 1][m + 1];
+ Bi = new int[3][n + 1][m + 1];
+ Bj = new int[3][n + 1][m + 1];
+ Bk = new int[3][n + 1][m + 1];
+ }
+
+ }
+
+ public void prepareAlignment(String sq1, String sq2) {
+
+ n = sq1.length();
+ m = sq2.length();
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+
+ allocateMatrices(n, m);
+ }
+
+ public void doAlignment(String sequence1, String sequence2, int startType, int endType) {
+ this.seq1 = sequence1;
+ this.seq2 = sequence2;
+ doAlignment(new Profile(0, sequence1), new Profile(0, sequence2), 0, 0, sequence1.length(), sequence2.length(), startType, endType);
+
+ }
+
+ public void doAlignment(Profile sequence1, Profile sequence2, int offset1, int offset2, int n, int m, int startType, int endType) {
+ doAlignment(sequence1, sequence2, offset1, offset2, n, m, startType, endType, false, false);
+ }
+
+ private int convertType(int type) {
+ if (type == TYPE_ANY) return type;
+ if (type == TYPE_X) return TYPE_Y;
+ if (type == TYPE_Y) return TYPE_X;
+ throw new RuntimeException("invalid type");
+ }
+
+ public void doAlignment(Profile sequence1, Profile sequence2, int offset1, int offset2, int n, int m, int startType, int endType, boolean freeStartGap, boolean freeEndGap) {
+ invert = false;
+ if (n > m) {
+ //swap the ordering, to prevent nasty allocation of matrices.
+ // for example, if we do a 100000 by 10 alignment, followed by a 10 x 100000 alignment, we end up
+ // allocating a 100000 x 100000 matrix
+ // This happens, when used by the NeedlemanWunschLinear
+ invert = true;
+ int temp;
+ temp = m;
+ m = n;
+ n = temp;
+ temp = offset1;
+ offset1 = offset2;
+ offset2 = temp;
+ startType = convertType(startType);
+ endType = convertType(endType);
+ Profile tempSequence;
+ tempSequence = sequence1;
+ sequence1 = sequence2;
+ sequence2 = tempSequence;
+ }
+// n = sequence1.length;
+// m = sequence2.length;
+ this.n = n;
+ this.m = m;
+
+// this.seq1 = sq1;
+// this.seq2 = sq2;
+
+ allocateMatrices(n, m);
+// }
+ /*prepareAlignment(sq1, sq2);
+ */
+
+// char[] s1 = seq1.toCharArray();
+// char[] s2 = seq2.toCharArray();
+
+// int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ float[][] M = F[0], Ix = F[1], Iy = F[2];
+ float val;
+ float s, a, b, c;
+
+ M[0][0] = 0;
+ Ix[0][0] = 0;
+ Iy[0][0] = 0;
+
+ for (int i = 1; i <= n; i++) {
+ float base = d;
+ if (startType == TYPE_X)
+ base = e;//if startType IS TYPE_X then we were already in a
+ // gap, so we can use gap extension penalty rather than gap starting penalty
+ Ix[i][0] = -base - e * (i - 1);
+ if (freeStartGap) Ix[i][0] = 0;
+// B[1][i][0].setTraceback(1, i-1, 0);
+ Bk[1][i][0] = 1;
+ Bi[1][i][0] = i - 1;
+ Bj[1][i][0] = 0;
+
+ }
+
+ for (int i = 1; i <= n; i++) {
+ Iy[i][0] = M[i][0] = Float.NEGATIVE_INFINITY;
+ }
+
+ for (int j = 1; j <= m; j++) {
+ float base = d;
+ if (startType == TYPE_Y)
+ base = e;//if startType IS TYPE_Y then we were already in a
+ // gap, so we can use gap extension penalty rather than gap starting penalty
+
+ Iy[0][j] = -base - e * (j - 1);
+ if (freeStartGap) Iy[0][j] = 0;
+// B[2][0][j].setTraceback(2, 0, j-1);
+ Bk[2][0][j] = 2;
+ Bi[2][0][j] = 0;
+ Bj[2][0][j] = j - 1;
+ }
+
+ for (int j = 1; j <= m; j++) {
+ Ix[0][j] = M[0][j] = Float.NEGATIVE_INFINITY;
+ }
+
+ for (int i = 1; i <= n; i++) {
+
+ for (int j = 1; j <= m; j++) {
+// s = score[s1[i-1]][s2[j-1]];
+// s = score[s1[i-1]][s2[j-1]];
+// s= sub.score [ sequence1.profile [i-1].characters[0]][ sequence2.profile [j-1].characters [0]];
+ s = ProfileCharacter.score(sequence1.profile[offset1 + i - 1], sequence2.profile[offset2 + j - 1], sub);
+ a = M[i - 1][j - 1] + s;
+ b = Ix[i - 1][j - 1] + s;
+ c = Iy[i - 1][j - 1] + s;
+
+ val = M[i][j] = max(a, b, c);
+ Bi[0][i][j] = i - 1;
+ Bj[0][i][j] = j - 1;
+ if (val == a) {
+ Bk[0][i][j] = 0;
+// B[0][i][j].setTraceback(0, i-1, j-1);
+ } else if (val == b) {
+ Bk[0][i][j] = 1;
+// B[0][i][j].setTraceback(1, i-1, j-1);
+ } else if (val == c) {
+ Bk[0][i][j] = 2;
+// B[0][i][j].setTraceback(2, i-1, j-1);
+ } else {
+ throw new Error("NWAffine 1");
+ }
+
+ float xd = d;
+ float xe = e;
+ if (j == m && freeEndGap) {
+ xd = 0;
+ xe = 0;
+ }
+
+ a = M[i - 1][j] - xd;
+ b = Ix[i - 1][j] - xe;
+ c = Iy[i - 1][j] - xd;
+ val = Ix[i][j] = max(a, b, c);
+
+
+ Bi[1][i][j] = i - 1;
+ Bj[1][i][j] = j;
+ if (val == a) {
+ Bk[1][i][j] = 0;
+// B[1][i][j].setTraceback(0, i-1, j);
+ } else if (val == b) {
+ Bk[1][i][j] = 1;
+// B[1][i][j].setTraceback(1, i-1, j);
+ } else if (val == c) {
+ Bk[1][i][j] = 2;
+// B[1][i][j].setTraceback(2, i-1, j);
+ } else {
+ throw new Error("NWAffine 2");
+ }
+
+ float yd = d;
+ float ye = e;
+ if (i == n && freeEndGap) {
+ yd = 0;
+ ye = 0;
+ }
+
+ a = M[i][j - 1] - yd;
+ b = Iy[i][j - 1] - ye;
+ c = Ix[i][j - 1] - yd;
+
+ val = Iy[i][j] = max(a, b, c);
+ Bi[2][i][j] = i;
+ Bj[2][i][j] = j - 1;
+ if (val == a) {
+ Bk[2][i][j] = 0;
+// B[2][i][j].setTraceback(0, i, j-1);
+ } else if (val == b) {
+ Bk[2][i][j] = 2;
+// B[2][i][j].setTraceback(2, i, j-1);
+ } else if (val == c) {
+ Bk[2][i][j] = 1;
+// B[2][i][j].setTraceback(1, i, j-1);
+ } else {
+ throw new Error("NWAffine 3");
+ }
+ }
+ }
+ // Find maximal score
+ int maxk = 0;
+ float maxval = F[0][n][m];
+ for (int k = 1; k < 3; k++) {
+ if (maxval < F[k][n][m]) {
+ maxval = F[k][n][m];
+ maxk = k;
+ }
+ }
+ if (endType == TYPE_X)
+ maxk = 1;
+ if (endType == TYPE_Y)
+ maxk = 2;
+
+ B0k = maxk;
+ B0i = n;
+ B0j = m;
+// B0 = new TracebackAffine(maxk, n, m);
+ }
+
+
+ public void appendMatch(AlignmentResult result1, AlignmentResult result2) {
+ String[] results = getMatch(null, null);
+ result1.append(results[0]);
+ result2.append(results[1]);
+
+ }
+
+ public String[] getMatch() {
+ return getMatch(seq1.toCharArray(), seq2.toCharArray());
+
+ }
+
+ public String[] getMatch(char[] sq1, char[]sq2) {
+
+
+ StringBuilder res1 = new StringBuilder();
+ StringBuilder res2 = new StringBuilder();
+ int tbi, tbj, tbk;
+// Traceback tb = B0;
+
+ int i = B0i;
+ int j = B0j;
+ int k = B0k;
+// int i = tb.i, j = tb.j;
+ while (i != 0 || j != 0) {
+ tbi = Bi[k][i][j];
+ tbj = Bj[k][i][j];
+ tbk = Bk[k][i][j];
+
+ if (i == tbi) {
+ res1.append('-');
+ } else {
+ if (sq1 != null)
+ res1.append(sq1[i - 1]);
+ else
+ res1.append('X');
+ }
+ if (j == tbj) {
+ res2.append('-');
+ } else {
+ if (sq2 != null)
+ res2.append(sq2[j - 1]);
+ else
+ res2.append('X');
+ }
+ i = tbi;
+ j = tbj;
+ k = tbk;
+ }
+ if (invert) {
+ return new String[]{res2.reverse().toString(), res1.reverse().toString()};
+ }
+ return new String[]{res1.reverse().toString(), res2.reverse().toString()};
+ }
+
+
+ public float getScore() {
+ return F[B0k][B0i][B0j];
+ }
+
+ private static final int TYPE_ANY = 0;
+ private static final int TYPE_X = 1;
+ private static final int TYPE_Y = 2;
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/NeedlemanWunschLinearSpace.java b/src/jebl/evolution/align/NeedlemanWunschLinearSpace.java
new file mode 100644
index 0000000..fc5b604
--- /dev/null
+++ b/src/jebl/evolution/align/NeedlemanWunschLinearSpace.java
@@ -0,0 +1,131 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: NeedlemanWunschLinearSpace.java 186 2006-01-24 00:41:22Z pepster $
+ */
+public class NeedlemanWunschLinearSpace extends AlignLinearSpace {
+
+ int u; // Halfway through seq1
+ int[][] c; // Best alignment from (0,0) to (i,j) passes through (u, c[i][j])
+
+ public NeedlemanWunschLinearSpace(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ super.prepareAlignment(sq1, sq2);
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ int n = this.n, m = this.m;
+ u = n/2;
+ c = new int[2][m+1];
+ float[][] score = sub.score;
+ for (int j=0; j<=m; j++) {
+ F[1][j] = -d * j;
+ }
+ float s, val;
+ for (int i=1; i<=n; i++) {
+ swap01(F); swap01(c);
+ // F[1] represents (new) column i and F[0] represents (old) column i-1
+ F[1][0] = -d * i;
+ for (int j=1; j<=m; j++) {
+ s = score[s1[i-1]][s2[j-1]];
+ val = max(F[0][j-1]+s, F[0][j]-d, F[1][j-1]-d);
+ F[1][j] = val;
+ if (i == u) {
+ c[1][j] = j;
+ } else {
+ if (val == F[0][j-1]+s) {
+ c[1][j] = c[0][j-1];
+ } else if (val == F[0][j]-d) {
+ c[1][j] = c[0][j];
+ } else if (val == F[1][j-1]-d) {
+ c[1][j] = c[1][j-1];
+ } else {
+ throw new Error("NWSmart 1");
+ }
+ }
+ }
+ }
+ }
+
+ public int getV() { return c[1][m]; }
+
+ public String[] getMatch() {
+ int v = getV();
+ if (n > 1 && m > 1) {
+ NeedlemanWunschLinearSpace al1, al2;
+ al1 = new NeedlemanWunschLinearSpace(sub, d);
+ al1.doAlignment(seq1.substring(0, u), seq2.substring(0, v));
+ al2 = new NeedlemanWunschLinearSpace(sub, d);
+ al2.doAlignment(seq1.substring(u), seq2.substring(v));
+ String[] match1 = al1.getMatch();
+ String[] match2 = al2.getMatch();
+ return new String[] { match1[0] + match2[0], match1[1] + match2[1] };
+ } else {
+ NeedlemanWunsch al = new NeedlemanWunsch(sub, d);
+ al.doAlignment(seq1, seq2);
+ return al.getMatch();
+ }
+ }
+
+ public void traceback(TracebackPlotter plotter) {
+
+ traceback(plotter, 0, 0, seq1, seq2);
+ }
+
+ public void traceback(TracebackPlotter plotter, int startx, int starty, String sq1, String sq2) {
+
+ List tracebacks = tracebackList(startx,starty);
+
+ plotter.newTraceBack(sq1, sq2);
+
+ for (int i = 0; i < tracebacks.size(); i++) {
+ Traceback traceback = (Traceback)tracebacks.get(i);
+ plotter.traceBack((Traceback)tracebacks.get(i));
+ }
+ plotter.finishedTraceBack();
+ }
+
+
+ private List tracebackList(int startx, int starty) {
+
+ List tracebacks = new ArrayList();
+
+ int v = getV();
+ if (n > 1 && m > 1) {
+ NeedlemanWunschLinearSpace al1, al2;
+ al1 = new NeedlemanWunschLinearSpace(sub, d);
+ al1.doAlignment(seq1.substring(0, u), seq2.substring(0, v));
+ al2 = new NeedlemanWunschLinearSpace(sub, d);
+ al2.doAlignment(seq1.substring(u), seq2.substring(v));
+ List tracebackList1 = al1.tracebackList(startx, starty);
+ List tracebackList2 = al2.tracebackList(startx+u,starty+v);
+ tracebackList1.addAll(tracebackList2);
+ return tracebackList1;
+ } else {
+ NeedlemanWunsch al = new NeedlemanWunsch(sub, d);
+ al.doAlignment(seq1, seq2);
+ return al.tracebackList(startx, starty);
+ }
+
+ }
+
+
+
+ public float getScore() { return F[1][m]; }
+}
diff --git a/src/jebl/evolution/align/NeedlemanWunschLinearSpaceAffine.java b/src/jebl/evolution/align/NeedlemanWunschLinearSpaceAffine.java
new file mode 100644
index 0000000..b556f6d
--- /dev/null
+++ b/src/jebl/evolution/align/NeedlemanWunschLinearSpaceAffine.java
@@ -0,0 +1,701 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.NucleotideScores;
+import jebl.evolution.align.scores.Scores;
+import jebl.evolution.align.scores.ScoresFactory;
+import jebl.evolution.alignments.BasicAlignment;
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceTester;
+import jebl.util.ProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// Global alignment using the Needleman-Wunsch algorithm (affine gap costs)
+// uses linear space.
+
+public class NeedlemanWunschLinearSpaceAffine extends AlignLinearSpaceAffine implements PairwiseAligner {
+ private float resultScore;
+ static final int RECURSION_THRESHOLD = 6;
+ private boolean debug = false;
+ private boolean freeGapsAtEnds;
+
+ public NeedlemanWunschLinearSpaceAffine(Scores sub, float openGapPenalty, float extendGapPenalty) {
+ this(sub, openGapPenalty, extendGapPenalty, false);
+ }
+
+
+ public NeedlemanWunschLinearSpaceAffine(Scores sub, float d, float e, boolean freeGapsAtEnds) {
+ this(sub, d, e,freeGapsAtEnds,false);
+// quadraticAlign = new NeedlemanWunschAffine(sub, d, e);
+ }
+
+ /**
+ *
+ * @param applyGapExtendCostToFirstGapResidue Generally there is an ambiguity in bioinformatics whether "gap opening" already includes the first gap character - in other words, whether a gap of length N has
+ <pre>
+ (a) a cost of gapOpen + N * gapExtend
+ or
+ (b) gapOpen + (N-1) * gapExtend.
+ </pre>.
+ <code>applyGapExtendCostToFirstGapResidue</code> should be true if using interpretation (a).
+
+
+ * @param sub
+ * @param d
+ * @param e
+ * @param freeGapsAtEnds
+ */
+ public NeedlemanWunschLinearSpaceAffine(Scores sub, float d, float e, boolean freeGapsAtEnds, boolean applyGapExtendCostToFirstGapResidue) {
+ super(Scores.includeAdditionalCharacters(sub,"_"), d+ (applyGapExtendCostToFirstGapResidue?e:0), e);
+ this.freeGapsAtEnds = freeGapsAtEnds;
+// quadraticAlign = new NeedlemanWunschAffine(sub, d, e);
+ }
+
+// private NeedlemanWunschAffine quadraticAlign;//we use the quadratic
+ // algorithm to calculate the alignment as the base recursion case.
+ String[] matchResult;
+ private static final int TYPE_ANY = 0;
+ private static final int TYPE_X = 1;
+ private static final int TYPE_Y = 2;
+ private int C[][][];
+ private int Ctype[][][];
+ private int previousm = -1, previousn = -1;
+
+ private ProgressListener progress;
+ private long totalProgress;
+ private long currentProgress;
+ private boolean cancelled;
+
+ int[][][] Bi, Bj, Bk;
+ private int allocatedn = -1;
+ private int allocatedm = -1;
+
+ public void allocateMatrices(int n, int m) {
+ //first time running this alignment. Create all new matrices.
+ if (n > allocatedn || m > allocatedm) {
+ n = maxi(n, allocatedn + 5);
+ m = maxi(m, allocatedm + 5);
+ allocatedn = n;
+ allocatedm = m;
+ Bi = new int[3][n + 1][m + 1];
+ Bj = new int[3][n + 1][m + 1];
+ Bk = new int[3][n + 1][m + 1];
+ }
+
+ }
+
+ private int convertType(int type) {
+ if (type == TYPE_ANY) return type;
+ if (type == TYPE_X) return TYPE_Y;
+ if (type == TYPE_Y) return TYPE_X;
+ throw new RuntimeException("invalid type");
+ }
+
+ private boolean addProgress(long value) {
+ currentProgress += value;
+ if (progress != null) {
+ double fraction = ((double) currentProgress) / totalProgress;
+ cancelled = progress.setProgress(fraction);
+ }
+ return cancelled;
+ }
+
+
+ public void doAlignment(String sq1, String sq2) {
+ doAlignment(sq1, sq2, null);
+ }
+
+// private AlignmentResult result1, result2;
+
+ public void doAlignment(String sq1, String sq2, ProgressListener progress, boolean scoreOnly) {
+ this.progress = progress;
+
+ sq1 = strip(sq1);
+ sq2 = strip(sq2);
+// prepareAlignment (sq1,sq2);
+ //we initialise the following arrays here rather than in prepareAlignment
+ //so that we do not have to create them again during recursion.
+ Profile profile1 = new Profile(0, sq1);
+ Profile profile2 = new Profile(0, sq2);
+ AlignmentResult[] results = doAlignment(profile1, profile2, progress, scoreOnly);
+ matchResult = new String[2];
+ if (cancelled) return;
+ matchResult[0] = Profile.buildAlignmentString(sq1, results[0]);
+ matchResult[1] = Profile.buildAlignmentString(sq2, results[1]);
+ }
+
+ public void doAlignment(String sq1, String sq2, ProgressListener progress) {
+ doAlignment(sq1, sq2, progress, false);
+ }
+
+ // todo: return null when progress canceled, and document and handle this behaviour
+ public AlignmentResult[] doAlignment(Profile profile1, Profile profile2,
+ ProgressListener progress, boolean scoreOnly) {
+ this.progress = progress;
+ if (freeGapsAtEnds && (profile1.getNumberOfSequences()>1 || profile2.getNumberOfSequences()>1)) {
+ profile1 = profile1.supportFreeEndGaps();
+ profile2 = profile2.supportFreeEndGaps();
+ }
+ this.n = profile1.length();
+ this.m = profile2.length();
+// System.out.println("aligning " + n + "," + m);
+ if (n > previousn || m > previousm) {
+ int maximum = Math.max(m,n);//would normally use "m", but "invert = true;" later on requires taking the maximum.
+
+ F = new float[3][2][ maximum + 1];
+ C = new int [3] [ 2] [maximum + 1];
+ Ctype = new int [3] [3] [maximum + 1];
+ previousn = n;
+ previousm = m;
+ }
+ totalProgress = ((long) n) * m * 2;
+// System.out.println("total =" + totalProgress + "," +n+ "," +m);
+ currentProgress = 0;
+ cancelled = false;
+ int maximumResultLength = m + n;
+ AlignmentResult result1 = new AlignmentResult(maximumResultLength);
+ AlignmentResult result2 = new AlignmentResult(maximumResultLength);
+ resultScore = doAlignment(profile1, profile2, 0, 0, n, m, TYPE_ANY, TYPE_ANY, result1, result2, scoreOnly, freeGapsAtEnds, freeGapsAtEnds);
+ return new AlignmentResult[]{result1, result2};
+ }
+
+ public String[] getMatch() {
+ return matchResult;
+ }
+
+ private static float gapFraction(ProfileCharacter character) {
+ float result = character.gapFraction();
+ //assert result < 1.0; //should not be calling this function on a profile that contains all gap Characters at one location.
+ // We do want to support stand-alone sequences with gaps. And it is possible for an extracted alignment to have all gaps. So this assertion has been removed.
+ return result;
+ }
+
+ private float doAlignment(Profile profile1, Profile profile2,
+ int offset1, int offset2, int n, int m, int startType, int endType,
+ AlignmentResult result1, AlignmentResult result2, boolean scoreOnly, boolean freeStartGap, boolean freeEndGap) {
+ this.n = n;
+ this.m = m;
+
+ boolean gapCostProduction = true;
+// int n = this.n, m = this.m;
+// float[][] score = sub.score;
+ float[][] M = F[0], Ix = F[1], Iy = F[2];
+ int[][] cm = C[0], cx = C[1], cy = C[2];
+ int[][] cmtype = Ctype[0], cxtype = Ctype[1], cytype = Ctype[2];
+ float val;
+ float s, a, b, c;
+ boolean calculateResults = false;
+ boolean invert = false;
+ if (debug) {
+
+ System.out.println("start =" + startType+ ", end=" + endType+ " free =" + freeStartGap+ "," + freeEndGap);
+ System.out.println("align from " + offset1 + " to " + (offset1 + n - 1) + " with from " + offset2 + " to " + (offset2 + m - 1));
+ System.out.println("s1=" + profile1.toString(offset1, n));
+ System.out.println("s2=" + profile2.toString(offset2, m));
+ }
+
+ if (n < RECURSION_THRESHOLD || m < RECURSION_THRESHOLD) {
+ calculateResults = true;
+ if (n > m) {
+ //swap the ordering, to prevent nasty allocation of matrices.
+ // for example, if we do a 100000 by 10 alignment, followed by a 10 x 100000 alignment, we end up
+ // allocating a 100000 x 100000 matrix
+// System.out.println("invert = true at " + offset1+","+offset2+ "," +n+ "," +m);
+ invert = true;
+ int temp;
+ temp = m;
+ m = n;
+ n = temp;
+ temp = offset1;
+ offset1 = offset2;
+ offset2 = temp;
+ startType = convertType(startType);
+ endType = convertType(endType);
+ Profile tempSequence;
+ tempSequence = profile1;
+ profile1 = profile2;
+ profile2 = tempSequence;
+
+ }
+ allocateMatrices(n, m);
+ }
+
+ int u = n / 2;
+
+ if (debug) {
+ System.out.println(" u=" + u);
+ }
+
+ //all that the remainder of this function does is to calculate the midpoint
+ //(u,v) that the optimal alignment passes through, along with the
+ //type of extension applied at the midpoint, in case it is the gap extension,
+ //in which case the recursion functions need to know that.
+
+ if (calculateResults) {
+ for (int i = 1; i <= n; i++) {
+ Bk[1][i][0] = 1;
+ Bi[1][i][0] = i - 1;
+ Bj[1][i][0] = 0;
+ }
+ }
+ //i corresponds to TYPE_X
+ //j corresponds to TYPE_Y
+ for (int j = 1; j <= m; j++) {
+ float base = d;
+ if (startType == TYPE_Y)
+ base = e;//if startType IS TYPE_Y then we were already in a
+ // gap, so we can use gap extension penalty rather than gap starting penalty
+ Iy[0][j] = - base - e * (j - 1);
+ if (freeStartGap) Iy[0][j] = 0;
+ Ix[0][j] = M[0][j] = Float.NEGATIVE_INFINITY;
+ cy[0][j] = 0;
+ cytype[0][j] = TYPE_Y;
+ if (calculateResults) {
+ Bk[2][0][j] = 2;
+ Bi[2][0][j] = 0;
+ Bj[2][0][j] = j - 1;
+ }
+ }
+ Ix[0][0] = Iy[0][0] = Float.NEGATIVE_INFINITY;
+ M[0][0] = 0;
+ cmtype[0][0] = 0;
+ cm[0][0] = 0;
+
+ swap01(Ix);
+ swap01(Iy);
+ swap01(M);
+ swap01(cm);
+ swap01(cx);
+ swap01(cy);
+ swap01(cmtype);
+ swap01(cxtype);
+ swap01(cytype);
+
+ for (int i = 1; i <= n; i++) {
+ swap01(Ix);
+ swap01(Iy);
+ swap01(M);
+ swap01(cm);
+ swap01(cx);
+ swap01(cy);
+ swap01(cmtype);
+ swap01(cxtype);
+ swap01(cytype);
+
+ M[1][0] = Float.NEGATIVE_INFINITY;
+ Iy[1][0] = Float.NEGATIVE_INFINITY;
+ float base = d;
+ if (startType == TYPE_X) base = e;
+ Ix[1][0] = - base - e * (i - 1);
+ if (freeStartGap) Ix[1][0] = 0;
+ cxtype[1][0] = TYPE_X;
+ cx[1][0] = 0;
+ for (int j = 1; j <= m; j++) {
+ if (cancelled) return 0;
+ s = ProfileCharacter.score(profile1.profile[offset1 + i - 1], profile2.profile[offset2 + j - 1], sub);
+ if (debug) {
+ System.out.println("loc=" + j + "," + i + " p1=" + profile1.profile[offset1 + i - 1] +
+ " p2=" + profile2.profile[offset2 + j - 1] + " score=" + s);
+ }
+ /* char c1= s1[i - 1];
+ char c2= s2[j - 1];
+
+ s = score[c1][c2];*/
+ a = M[0][j - 1] + s;
+ b = Ix[0][j - 1] + s;
+ c = Iy[0][j - 1] + s;
+
+ val = M[1][j] = max(a, b, c);
+ if (calculateResults) {
+ int k = 0;
+ if (val == b) k = 1;
+ if (val == c) k = 2;
+ Bi[0][i][j] = i - 1;
+ Bj[0][i][j] = j - 1;
+ Bk[0][i][j] = k;
+ }
+ if (i == u) {
+ cm[1][j] = j;
+ cmtype[1][j] = TYPE_ANY;
+ } else if (i > u) {
+ if (val == a) {
+ cm[1][j] = cm[0][j - 1];
+ cmtype[1][j] = cmtype[0][j - 1];
+ } else if (val == b) {
+ cm[1][j] = cx[0][j - 1];
+ cmtype[1][j] = cxtype[0][j - 1];
+ } else if (val == c) {
+ cm[1][j] = cy[0][j - 1];
+ cmtype[1][j] = cytype[0][j - 1];
+ } else {
+ throw new Error("NWAffine 1");
+ }
+ }
+
+ //introduce a gap by skipping a character in the i direction
+
+ float xd = d;
+ float xe = e;
+ if (j == m && freeEndGap) {
+ xd = 0;
+ xe = 0;
+ }
+ float gapFraction = gapFraction(profile1.profile[offset1 + i - 1]);
+ float ownGapFraction = gapFraction(profile2.profile[offset2 + j - 1]);
+ if (gapCostProduction && gapFraction > 0) {
+ // if the other sequence that we are aligning a gap to
+ // already had some gaps in it, proportionally reduce the gap cost.
+// xd= xd*(1- gapFraction);
+ xd = xd - xe * gapFraction;
+ xe = xe * (1 - gapFraction);
+ }
+ if (ownGapFraction > 0) {
+ //if our own sequence already has some gaps following the proposed
+ // insertion of a gap at this point, then reduce the gap opening
+ // penalty to a point such that if the sequence contained entirely Characters
+ // at the next position (which is impossible) then the
+ // gap opening cost would reduce to the same as the gap Extension cost
+// xd= xe+(xd-xe)*(1- ownGapFraction);
+ }
+ a = M[0][j] - xd;
+ b = Ix[0][j] - xe;
+ c = Iy[0][j] - xd;
+ val = Ix[1][j] = max(a, b, c);
+ if (calculateResults) {
+ int k = 0;
+ if (val == b) k = 1;
+ if (val == c) k = 2;
+ Bi[1][i][j] = i - 1;
+ Bj[1][i][j] = j;
+ Bk[1][i][j] = k;
+ }
+ if (i == u) {
+ cx[1][j] = j;
+ cxtype[1][j] = TYPE_X;
+ } else if (i > u) {
+ if (val == a) {
+ cx[1][j] = cm[0][j];
+ cxtype[1][j] = cmtype[0][j];
+ } else if (val == b) {
+ cx[1][j] = cx[0][j];
+ cxtype[1][j] = cxtype[0][j];
+ } else if (val == c) {
+ cx[1][j] = cy[0][j];
+ cxtype[1][j] = cytype[0][j];
+ } else {
+ throw new Error("NWAffine 2");
+ }
+ }
+
+ //introduce a gap by skipping a character in the j direction
+ float yd = d;
+ float ye = e;
+ if (i == n && freeEndGap) {
+ yd = 0;
+ ye = 0;
+ }
+ ownGapFraction = gapFraction(profile1.profile[offset1 + i - 1]);
+ gapFraction = gapFraction(profile2.profile[offset2 + j - 1]);
+ if (gapCostProduction && gapFraction > 0) {
+ // if the other sequence that we are aligning a gap to
+ // already had some gaps in it, proportionally reduce the gap cost.
+// yd = yd * (1 - gapFraction);
+ yd = yd - ye * gapFraction;
+ ye = ye * (1 - gapFraction);
+
+ }
+ if (ownGapFraction > 0) {
+ //if our own sequence already has some gaps following the proposed
+ // insertion of a gap at this point, then reduce the gap opening
+ // penalty to a point such that if the sequence contained entirely Characters
+ // at the next position (which is impossible) then the
+ // gap opening cost would reduce to the same as the gap Extension cost
+// yd = ye + (yd - ye) * (1 - ownGapFraction);
+ }
+ a = M[1][j - 1] - yd;
+ b = Iy[1][j - 1] - ye;
+ c = Ix[1][j - 1] - yd;
+ val = Iy[1][j] = max(a, b, c);
+ if (calculateResults) {
+ int k = 0;
+ if (val == b) k = 2;
+ if (val == c) k = 1;
+ Bi[2][i][j] = i;
+ Bj[2][i][j] = j - 1;
+ Bk[2][i][j] = k;
+ }
+ if (i == u) {
+ cy[1][j] = j;
+ cytype[1][j] = TYPE_Y;
+ } else if (i > u) {
+ if (val == a) {
+ cy[1][j] = cm[1][j - 1];
+ cytype[1][j] = cmtype[1][j - 1];
+ } else if (val == b) {
+ cy[1][j] = cy[1][j - 1];
+ cytype[1][j] = cytype[1][j - 1];
+ } else if (val == c) {
+ cy[1][j] = cx[1][j - 1];
+ cytype[1][j] = cxtype[1][j - 1];
+ } else {
+ throw new Error("NWAffine 3");
+ }
+ }
+
+ }
+ if (addProgress(m)) return 0;
+ }
+ // Find maximal score
+ int bestk = 0;
+ for (int k = 1; k < 3; k++) {
+ if (F[k][1][m] > F[bestk][1][m])
+ bestk = k;
+ }
+
+ //if the alignment must end with a particular type, force that type to be selected:
+ //System.out.println("end type ="+endType);
+ if (endType == TYPE_X)
+ bestk = 1;
+ if (endType == TYPE_Y)
+ bestk = 2;
+ assert F[bestk][1][m]>Float.NEGATIVE_INFINITY;
+
+ int v = C[bestk][1][m];
+ int vtype = Ctype[bestk][1][m];
+ if (debug) {
+ System.out.println("bestk=" + bestk + " v=" + v + " vtype =" + vtype);
+ }
+ float finalScore = F[bestk][1][m];
+
+
+ if (freeEndGap && n == 0) finalScore = 0;
+ if (freeStartGap && n == 0) finalScore = 0;
+
+ if (scoreOnly) return finalScore;
+
+ if (calculateResults) {
+ //System.out.println("append results " +n+ "," +m+ "," +bestk);
+ appendResults(invert, result1, result2, n, m, bestk);
+ } else {
+ boolean propagateFreeEndGap = freeEndGap && (u == n || v == m);
+ boolean propagateFreeStartGap = freeStartGap && (u == 0 || v == 0);
+ float score1=doAlignment(profile1, profile2, offset1, offset2, u, v, startType, vtype, result1, result2, false, freeStartGap, propagateFreeEndGap);
+ if (cancelled) return 0;
+ float score2=doAlignment(profile1, profile2, offset1 + u, offset2 + v, n - u, m - v, vtype, endType, result1, result2, false, propagateFreeStartGap, freeEndGap);
+ if (cancelled) return 0;
+ float combinedScore = score1+ score2;
+ if ( finalScore != 0 && Math.abs((combinedScore - finalScore)/finalScore) > 1e-5 ) {
+ // multiple alignment scoring is not necessarily valid.
+ if (profile1.sequenceCount==1 && profile2.sequenceCount==1) {
+ //todo: work out why this happens sometimes, possibly just cumulative floating point error.
+ System.out.println("free =" + freeStartGap+ "," + freeEndGap);
+ System.out.println("offset1="+ offset1+" offset2="+ offset2+" u="+u+" v="+v);
+ System.out.println(""+ score1+ "+" + score2+"!="+ finalScore);
+ }
+ }
+ }
+ //System.out.println("free =" +freeStartGap+ "," + freeEndGap+ ",score =" + finalScore);
+
+ /* String sequence1a = sq1.substring(0, u);
+ String sequence2a = sq2.substring(0, v);
+ String[] match1= doAlignment(sequence1a,sequence2a,startType,vtype );
+ if(cancelled) return null;
+ float match1Score= getScore();
+ String sequence1b = sq1.substring(u);
+ String sequence2b = sq2.substring(v);
+ String[] match2= doAlignment(sequence1b,sequence2b,vtype, endType );
+ if (cancelled) return null;
+ float match2Score = getScore();
+ float combineScore = match1Score + match2Score;*/
+
+ //I thought the following would be a good idea to test how well it is working,
+ // but in practice
+ // the floatingpoint error builds up to exceed small amounts
+ // even on my test caseof only a few hundred characters
+ /*
+ if (Math.abs(combineScore - resultScore)> 0.0001f) {
+ System.out.println (sequence1a+ "+" + sequence1b);
+ System.out.println (sequence2a+ "+" + sequence2b);
+
+ String message = "final score doesn't match (" + match1Score + "+" + match2Score + "=" + (match2Score + match1Score)+ "!=" + resultScore + ")";
+ System.out.println (message);
+ System.out.println (match1[0]);
+ System.out.println (match1[1]);
+ System.out.println (match2[0]);
+ System.out.println (match2[1]);
+ NeedlemanWunschAffine align = new NeedlemanWunschAffine(sub, d, e);
+ align.doAlignment(sq1, sq2, startType, endType);
+ System.out.println ("score from quadratic algorithm =" +align.getScore());
+ String[] match3=align.getMatch();
+ System.out.println(match3[0]);
+ System.out.println(match1[0] + match2[0]);
+ System.out.println(match3[1]);
+ System.out.println(match1[1]+match2[1]);
+
+
+ throw new Error (message);
+ }
+ */
+// setScore (resultScore);
+
+// setScore (combineScore);
+// return new String[] {match1[0]+ match2[0], match1[1]+ match2[1]};
+ return finalScore;
+ }
+
+ private void appendResults(boolean invert, AlignmentResult result1, AlignmentResult result2, int n, int m, int bestk) {
+
+ StringBuilder res1 = new StringBuilder();
+ StringBuilder res2 = new StringBuilder();
+ int tbi, tbj, tbk;
+
+ int i = n;
+ int j = m;
+ int k = bestk;
+ while (i != 0 || j != 0) {
+ tbi = Bi[k][i][j];
+ tbj = Bj[k][i][j];
+ tbk = Bk[k][i][j];
+
+ if (i == tbi) {
+ res1.append('-');
+ } else {
+ res1.append('X');
+ }
+ if (j == tbj) {
+ res2.append('-');
+ } else {
+ res2.append('X');
+ }
+ i = tbi;
+ j = tbj;
+ k = tbk;
+ }
+ String string1 = res1.reverse().toString();
+ String string2 = res2.reverse().toString();
+// System.out.println("string 1 =" + string1);
+// System.out.println("string 2 =" + string2);
+ if (invert) {
+ result1.append(string2);
+ result2.append(string1);
+ } else {
+ result1.append(string1);
+ result2.append(string2);
+ }
+// result1.print ();
+// result2.print ();
+
+ }
+
+ @Override
+ public float getScore() {
+ return resultScore;
+ }
+
+ /* private void setScore(float resultScore) {
+ this.resultScore=resultScore;
+ }*/
+
+ public static void main(String[] arguments) {
+ Scores scores = ScoresFactory.generateScores("Blosum45");
+
+ String sequence1 = SequenceTester.getTestSequence1(arguments);
+ String sequence2 = SequenceTester.getTestSequence2(arguments);
+
+ sequence1="GTGGCAA---------AAAACATTCAAGCCATTCGCGGCATGAACGATTACCTGCCTGGCGAA---------------------ACGGCCATCTGGCAGCGCATTGAAGGCACACTGAAAAACGTGCTCGGCAGCTACGGTTACAGTGAAATCCGCTTGCCGATTGTAGAGCAGACCCCGCTATTCAAACGTGCGATTGGTGAAGTCACCGACGTGGTTGAAAAAGAGATGTACACCTTTGAGGATCGCAATGGCGACAG---CCTGACTCTGCGCCCTGAAGGGACGGCGGGCTGTGTACGCGCCGGCATCGAGCATGGTCTTCTGTACAAT---CAGGAACAGCGTCTGTGGTATATCGGGCCGATGTTCCGTCACGAGCGTCCGCAGAAAGGGCGTTATCGTCAGTTCCATCAGTTGGGCTGCGAAGTTTTCGGTCTGCAAGGTCCGGATATCGACGCTGAACTGATTAT [...]
+ sequence1 = "GTGGCAAAAAACATTCAAGCCATTCGCGGCA";//TGAACGATTACCTGCCTGGCGAAACGGCCATCTGGCAGCGCATTGAAGGCACACTGAAAAACGTGCTCGGCAGCTACGGTTACAGTGAAATCCGCTTGCCGATTGTAGAGCAGACCCCGCTATTCAAACGTGCGATTGGTGAAGTCACCGACGTGGTTGAAAAAGAGATGTACACCTTTGAGGATCGCAATGGCGACAGCCTGACTCTGCGCCCTGAAGGGACGGCGGGCTGTGTACGCGCCGGCATCGAGCATGGTCTTCTGTACAATCAGGAACAGCGTCTGTGGTATATCGGGCCGATGTTCCGTCACGAGCGTCCGCAGAAAGGGCGTTATCGTCAGTTCCATCAGTTGGGCTGCGAAGTTTTCGGTCTGCAAGGTCCGGATATCGACGCTGAACTGATTATGCTCACTGCCCGCTGGTGGCGCGCGCTGGG [...]
+ sequence2="GCCTGTCGCCCGACAACATCATCCTGTCGTGCAAGGTCAGCAATGTGCAGGACCTGATCAGCGTC";//TACCGCGAGCTCGGCGGTCGCTGCGACTACCCGCTGCACCTGGGCCTGACCGAGGCCGGCATGGGCAGCAAGGGCATCGTCGCCTCCAGCGCCGCGCTGGCCGTGTTGTTGCAGGAAGGCATAGGCGACACCATCCGCATCTCGCTGACGCCGCAGCCGGGCGAGGCGCGCACCAAGGAGGTGGTGGTCGCGCAGGAGTTGCTGCAGACCATGGGCCTGCGCAGCTTCACGCCGCTGGTCACCGCCTGTCCGGGCTGCGGCCGCACCACCAGCACCTTCTTCCAGGAGCTGGCCGATCATATCCAGAGCTATCTGCGCGAGCGGATGCCGGTGTGGCGGCTGCAGTATCCGGGCGTCGAAGACATGAAGGTGGCGGTGATGGGCTGCGTGGTCAACGGTCCGGG [...]
+ scores = new NucleotideScores(5,-4);
+
+ float e = 0.1f;
+ float d = 1.0f;
+
+ System.out.println("aligning sequence of length " + sequence1.length() + " with sequence of length " + sequence2.length());
+
+
+ long start;
+ long end;
+ final int repeat = 1;
+
+ start = System.currentTimeMillis();
+ String[] results2 = null, results = null, results3 = null;
+ NeedlemanWunschAffine align2 = null;
+ NeedlemanWunschLinearSpaceAffine align = null;
+ OldNeedlemanWunschAffine align3 = null;
+
+// for (int i = 0; i < repeat; i++) {
+// align2 = new NeedlemanWunschAffine(scores, d, e);
+// align2.doAlignment(sequence1, sequence2);
+//
+// results2 = align2.getMatch();
+// }
+ end = System.currentTimeMillis();
+ System.out.println("quadratic space took " + (end - start) + " milliseconds");
+
+ start = System.currentTimeMillis();
+ for (int i = 0; i < repeat; i++) {
+ align = new NeedlemanWunschLinearSpaceAffine(scores, d, e);
+ align.doAlignment(sequence1, sequence2);
+ results = align.getMatch();
+ }
+ end = System.currentTimeMillis();
+ System.out.println("linear space took " + (end - start) + " milliseconds");
+//if(true)return;
+ start = System.currentTimeMillis();
+ for (int i = 0; i < repeat; i++) {
+ align3 = new OldNeedlemanWunschAffine(scores, d, e);
+ align3.doAlignment(sequence1, sequence2);
+
+ results3 = align3.getMatch();
+ }
+ end = System.currentTimeMillis();
+ System.out.println("old quadratic space took " + (end - start) + " milliseconds");
+ System.out.println(results[0]);
+ System.out.println(results3[0]);
+// System.out.println(results3[0]);
+ System.out.println(results[1]);
+ System.out.println(results3[1]);
+// System.out.println (results3[1]);
+ float score = align.getScore();
+ float score3 = align3.getScore();
+ if (results[0].equals(results3[0]) && results[1].equals(results3[1]))
+ System.out.println("results are the same");
+ else
+ System.out.println("results are different");
+ System.out.println("score 1 =" + score);
+ System.out.println("score 2 =" + score3);
+
+ SmithWatermanLinearSpaceAffine align4 = null;
+ start = System.currentTimeMillis();
+ for (int i = 0; i < repeat; i++) {
+ align4 = new SmithWatermanLinearSpaceAffine(scores, d, e);
+ align4.doAlignment(sequence1, sequence2);
+ align4.getMatch();
+ }
+ end = System.currentTimeMillis();
+ System.out.println("SmithWaterman linear space affine space took " + (end - start) + " milliseconds");
+ }
+
+ public void setDebug(boolean display) {
+ debug = display;
+ }
+
+ public Result doAlignment(Sequence seq1, Sequence seq2, ProgressListener progress) {
+ doAlignment(seq1.getString(), seq2.getString(), progress);
+ if (progress.setProgress(1)) return null;
+ List<Sequence> seqs = new ArrayList<Sequence>(2);
+ String[] results = getMatch();
+ seqs.add(new BasicSequence(seq1.getSequenceType(), seq1.getTaxon(), results[0]));
+ seqs.add(new BasicSequence(seq2.getSequenceType(), seq2.getTaxon(), results[1]));
+ return new Result(new BasicAlignment(seqs), getScore());
+ }
+
+ public double getScore(Sequence seq1, Sequence seq2) {
+ doAlignment(seq1.getString(), seq2.getString(), null, true);
+ return getScore();
+ }
+}
+
+
diff --git a/src/jebl/evolution/align/NonOverlapMultipleLocalAffine.java b/src/jebl/evolution/align/NonOverlapMultipleLocalAffine.java
new file mode 100644
index 0000000..b9846b8
--- /dev/null
+++ b/src/jebl/evolution/align/NonOverlapMultipleLocalAffine.java
@@ -0,0 +1,191 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+import java.util.ArrayList;
+
+/**
+ * Performs recursive local alignments. each time splitting the longer of the two sequences
+ * into two subsequences either side of the local alignment and aligning those.
+ * Uses SmithWatermanLinearSpaceAffine. Stores all of the local alignments and their scores.
+ * Threshold T is the minimum score that an alignment must be for inclusion.
+ *
+ * @author Richard Moir
+ *
+ * @version $Id: NonOverlapMultipleLocalAffine.java 370 2006-06-29 18:57:56Z rambaut $
+ *
+ */
+
+public class NonOverlapMultipleLocalAffine extends AlignRepeatAffine {
+
+ private ArrayList<LocalAlignment> localAligns = new ArrayList<LocalAlignment>();
+ private SmithWatermanLinearSpaceAffine swlsa;
+
+ public NonOverlapMultipleLocalAffine(Scores sub, float d, float e, int T) {
+ super(sub, d, e, T);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ if(sq1.length() < sq2.length()) { //sq2 is the shorter seq (is not dissected).
+ String temp = sq2;
+ sq2 = sq1;
+ sq1 = temp;
+ }
+ this.seq1 = sq1;
+ this.seq2 = sq2;
+
+ swlsa = new SmithWatermanLinearSpaceAffine(sub, d, e);
+ swlsa.doAlignment(sq1, sq2);
+
+ String[] match = swlsa.getMatch();
+ if(swlsa.getScore() >= T) {
+ localAligns.add(new LocalAlignment(match[0],match[1],swlsa.start1,swlsa.end1 - 1,swlsa.getScore()));
+ if(swlsa.start1 != 0)
+ recurseAlignment(sq1.substring(0, swlsa.start1), 0);
+ if(swlsa.end1 != sq1.length())
+ recurseAlignment(sq1.substring(swlsa.end1, sq1.length()), swlsa.end1);
+ }
+ }
+
+ public void recurseAlignment(String sq1, int leftIndex) {
+
+ swlsa.doAlignment(sq1, seq2);
+
+ String match[] = swlsa.getMatch();
+ if(swlsa.getScore() >= T) {
+ localAligns.add(new LocalAlignment(match[0],match[1],swlsa.start1 + leftIndex,swlsa.end1 - 1 + leftIndex,swlsa.getScore()));
+ }
+ else return;
+
+ if(swlsa.start1 != 0)
+ recurseAlignment(sq1.substring(0, swlsa.start1), leftIndex);
+ if(swlsa.end1 != sq1.length())
+ recurseAlignment(sq1.substring(swlsa.end1, sq1.length()), leftIndex + swlsa.end1);
+
+ }
+
+ /**
+ * @return two-element array containing all of the local alignments separated by " - ";
+ */
+ public String[] getMatch() {
+ String sq1 = "";
+ String sq2 = "";
+ for (LocalAlignment localAlign : localAligns) {
+ sq1 += localAlign.sq1 + " - ";
+ sq2 += localAlign.sq2 + " - ";
+ }
+ return new String[] {sq1.substring(0,sq1.length() - 3), sq2.substring(0,sq1.length() - 3)};
+ }
+
+ /**
+ * @param width length to trim lines to. -1 = infinite width.
+ * @return String containing all local alignments with the score next to them.
+ */
+ public String getMatchScores(int width) {
+ String sq1;
+ String sq2;
+ float score;
+ String matchScores = "";
+ for (LocalAlignment la : localAligns) {
+ sq1 = la.sq1;
+ sq2 = la.sq2;
+ score = la.score;
+ if (la.sq1.length() < width) {
+ matchScores += "Score: " + score + "\n";
+ matchScores += sq1 + "\n";
+ matchScores += sq1 + "\n\n\n";
+ } else {
+ matchScores += "Score: " + score + "\n";
+ int size = sq1.length();
+ for (int i = width; i < size + width; i += width) {
+ if (i > size) {
+ matchScores += sq1.substring(i - width, size) + "\n";
+ matchScores += sq2.substring(i - width, size) + "\n\n";
+ } else {
+ matchScores += sq1.substring(i - width, i) + "\n";
+ matchScores += sq2.substring(i - width, i) + "\n\n";
+ }
+ }
+ matchScores += "\n";
+ }
+ }
+ return matchScores;
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() {
+ float score = 0;
+ for (LocalAlignment la : localAligns) {
+ score += la.score;
+ }
+ return score;
+ }
+
+ /**
+ * The indices for these correspond to those for the getAlignments() matrix.
+ *
+ * @return a matrix of scores for all the alignments.
+ */
+ public float[] getScores() {
+ float[] scores = new float[localAligns.size()];
+ int i = 0;
+ for (LocalAlignment la : localAligns) {
+ scores[i] = la.score;
+ i++;
+ }
+ return scores;
+ }
+
+ /**
+ * The indices for these correspond to those for the getScoreMatrix() matrix.
+ *
+ * @return a 2d matrix of alignments. first index is the alignment number. second is the sequence number (0 or 1)
+ */
+ public String[][] getAlignments() {
+ String[][] aligns = new String[localAligns.size()][2];
+ int i = 0;
+ for (Object localAlign : localAligns) {
+ LocalAlignment la = (LocalAlignment) localAlign;
+ aligns[i][0] = la.sq1;
+ aligns[i][2] = la.sq2;
+ i++;
+ }
+ return aligns;
+ }
+
+ /**
+ * Print matrix used to calculate this alignment.
+ *
+ * @param out Output to print to.
+ */
+ public void printf(Output out) {
+ swlsa.printf(out);
+ }
+}
+
+/**
+ * Class used to store all of the local alignments.
+ */
+class LocalAlignment {
+
+ public String sq1;
+ public String sq2;
+ public int start;
+ public int end;
+ public float score;
+
+ public LocalAlignment(String sq1, String sq2, int start, int end, float score) {
+ this.sq1 = sq1;
+ this.sq2 = sq2;
+ this.start = start;
+ this.end = end;
+ this.score = score;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/OldNeedlemanWunschAffine.java b/src/jebl/evolution/align/OldNeedlemanWunschAffine.java
new file mode 100644
index 0000000..1a79967
--- /dev/null
+++ b/src/jebl/evolution/align/OldNeedlemanWunschAffine.java
@@ -0,0 +1,108 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+// Global alignment using the Needleman-Wunsch algorithm (affine gap costs)
+
+public class OldNeedlemanWunschAffine extends AlignAffine {
+
+ public OldNeedlemanWunschAffine(Scores sub, float d, float e) {
+ super(sub, d, e);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ prepareAlignment(sq1, sq2);
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ float[][] M = F[0], Ix = F[1], Iy = F[2];
+ float val;
+ float s, a, b, c;
+
+ for (int i=1; i<=n; i++) {
+ Ix[i][0] = -d - e * (i-1);
+ B[1][i][0].setTraceback(1, i-1, 0);
+ }
+
+ for (int i=1; i<=n; i++) {
+ Iy[i][0] = M[i][0] = Float.NEGATIVE_INFINITY;
+ }
+
+ for (int j=1; j<=m; j++) {
+ Iy[0][j] = -d - e * (j-1);
+ B[2][0][j].setTraceback(2, 0, j-1);
+ }
+
+ for (int j=1; j<=m; j++) {
+ Ix[0][j] = M[0][j] = Float.NEGATIVE_INFINITY;
+ }
+
+ for (int i=1; i<=n; i++) {
+
+ for (int j=1; j<=m; j++) {
+ s = score[s1[i-1]][s2[j-1]];
+ a = M[i-1][j-1]+s;
+ b = Ix[i-1][j-1]+s;
+ c = Iy[i-1][j-1]+s;
+
+ val = M[i][j] = max(a, b, c);
+ if (val == a) {
+ B[0][i][j].setTraceback(0, i-1, j-1);
+ } else if (val == b) {
+ B[0][i][j].setTraceback(1, i-1, j-1);
+ } else if (val == c) {
+ B[0][i][j].setTraceback(2, i-1, j-1);
+ } else {
+ throw new Error("NWAffine 1");
+ }
+
+ a = M[i-1][j]-d;
+ b = Ix[i-1][j]-e;
+ c = Iy[i-1][j]-d;
+ val = Ix[i][j] = max(a, b, c);
+
+ if (val == a) {
+ B[1][i][j].setTraceback(0, i-1, j);
+ } else if (val == b) {
+ B[1][i][j].setTraceback(1, i-1, j);
+ } else if (val == c) {
+ B[1][i][j].setTraceback(2, i-1, j);
+ } else {
+ throw new Error("NWAffine 2");
+ }
+
+ a = M[i][j-1]-d;
+ b = Iy[i][j-1]-e;
+ c = Ix[i][j-1]-d;
+ val = Iy[i][j] = max(a, b, c);
+ if (val == a) {
+ B[2][i][j].setTraceback(0, i, j-1);
+ } else if (val == b) {
+ B[2][i][j].setTraceback(2, i, j-1);
+ } else if (val == c) {
+ B[2][i][j].setTraceback(1, i, j-1);
+ } else {
+ throw new Error("NWAffine 3");
+ }
+ }
+ }
+ // Find maximal score
+ int maxk = 0;
+ float maxval = F[0][n][m];
+ for (int k=1; k<3; k++) {
+ if (maxval < F[k][n][m]) {
+ maxval = F[k][n][m];
+ maxk = k;
+ }
+ }
+ B0 = new TracebackAffine(maxk, n, m);
+ }
+}
diff --git a/src/jebl/evolution/align/Output.java b/src/jebl/evolution/align/Output.java
new file mode 100644
index 0000000..1917023
--- /dev/null
+++ b/src/jebl/evolution/align/Output.java
@@ -0,0 +1,8 @@
+package jebl.evolution.align;
+
+public abstract class Output {
+
+ public abstract void print(String s);
+ public abstract void println(String s);
+ public abstract void println();
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/OverlapAlign.java b/src/jebl/evolution/align/OverlapAlign.java
new file mode 100644
index 0000000..8f58de0
--- /dev/null
+++ b/src/jebl/evolution/align/OverlapAlign.java
@@ -0,0 +1,56 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+// Overlap matching (simple gap costs)
+
+public class OverlapAlign extends AlignSimple {
+
+ public OverlapAlign(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ super.prepareAlignment(sq1, sq2);
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ // F[0][0..m] = F[0..n][0] = 0 by construction
+ for (int i=1; i<=n; i++)
+ for (int j=1; j<=m; j++) {
+ float s = score[seq1.charAt(i-1)][seq2.charAt(j-1)];
+ float val = max(F[i-1][j-1]+s, F[i-1][j]-d, F[i][j-1]-d);
+ F[i][j] = val;
+ if (val == F[i-1][j-1]+s)
+ B[i][j].setTraceback(i-1, j-1);
+ else if (val == F[i-1][j]-d)
+ B[i][j].setTraceback(i-1, j);
+ else if (val == F[i][j-1]-d)
+ B[i][j].setTraceback(i, j-1);
+ else
+ throw new Error("RM 1");
+ }
+ // Find maximal score on right-hand and bottom borders
+ int maxi = -1, maxj = -1;
+ float maxval = Float.NEGATIVE_INFINITY;
+ for (int i=0; i<=n; i++)
+ if (maxval < F[i][m]) {
+ maxi = i;
+ maxval = F[i][m];
+ }
+ for (int j=0; j<=m; j++)
+ if (maxval < F[n][j]) {
+ maxj = j;
+ maxval = F[n][j];
+ }
+ if (maxj != -1) // the maximum score was F[n][maxj]
+ B0 = new TracebackSimple(n, maxj);
+ else // the maximum score was F[maxi][m]
+ B0 = new TracebackSimple(maxi, m);
+ }
+}
diff --git a/src/jebl/evolution/align/PairwiseAligner.java b/src/jebl/evolution/align/PairwiseAligner.java
new file mode 100644
index 0000000..c7e7cc1
--- /dev/null
+++ b/src/jebl/evolution/align/PairwiseAligner.java
@@ -0,0 +1,27 @@
+package jebl.evolution.align;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.alignments.Alignment;
+import jebl.util.ProgressListener;
+
+/**
+ * @author Joseph Heled
+ * @version $Id: PairwiseAligner.java 315 2006-05-03 02:13:54Z alexeidrummond $
+ *
+ */
+public interface PairwiseAligner {
+
+ public class Result {
+ final public Alignment alignment;
+ final public double score;
+
+ Result(Alignment alignment, double score) {
+ this.alignment = alignment;
+ this.score = score;
+ }
+ }
+
+ Result doAlignment(Sequence seq1, Sequence seq2, ProgressListener progress);
+
+ double getScore(Sequence seq1, Sequence seq2);
+}
diff --git a/src/jebl/evolution/align/Profile.java b/src/jebl/evolution/align/Profile.java
new file mode 100644
index 0000000..b9f8a09
--- /dev/null
+++ b/src/jebl/evolution/align/Profile.java
@@ -0,0 +1,331 @@
+package jebl.evolution.align;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Matt Kearse
+ * @version $Id: Profile.java 706 2007-05-09 02:47:48Z matt_kearse $
+ *
+ * Represents a profile of a number of sequences to be used in a
+ * multiple sequence alignment.
+ */
+class Profile {
+ ProfileCharacter[] profile;
+ private int alphabetSize;
+// int length;
+ int sequenceCount;
+ private boolean automaticallyCalculatedAlphabetSize = false;
+ private Map<Integer, String> paddedSequences = new HashMap<Integer, String>();
+ private boolean supportsFreeEndGaps=false;
+
+ public String getSequence( int sequenceNumber) {
+ return paddedSequences.get(sequenceNumber);
+ }
+
+
+ public Profile(int alphabetSize) {
+ this.alphabetSize = alphabetSize;
+ }
+
+ /**
+ * Should only be used for constructing a profile that will not
+ * have other sequences added.
+ *
+ * Correct usage in other cases is to do
+ * new Profile( calculateAlphabetSize(sequences));
+ *
+ * @param sequenceNumber
+ * @param sequence
+ */
+ public Profile(int sequenceNumber,String sequence) {
+ this(calculateAlphabetSize(new String[] {sequence}));
+ addSequence(sequenceNumber,sequence);
+ automaticallyCalculatedAlphabetSize = true;
+ }
+
+ public Profile(Alignment alignment, int alphabetSize) {
+ this(alignment, alphabetSize , 0);
+ }
+
+ public Profile(Alignment alignment, int alphabetSize, int offset) {
+ this.alphabetSize = alphabetSize;
+ final List<Sequence> sequenceList = alignment.getSequenceList();
+ for(int i = 0; i < sequenceList.size(); ++i) {
+ addSequence(i + offset, sequenceList.get(i).getString());
+ }
+ }
+
+ /**
+ * Get the number of sequences in this profile.
+ * @return the number of sequences in this profile.
+ */
+ public int getNumberOfSequences() {
+ return sequenceCount;
+ }
+ /**
+ * Get the number of residues in each sequence in the profile
+ * @return the number of residues in each sequence in the profile
+ */
+ public int length () {
+ return profile.length;
+ }
+
+ private static ProfileCharacter[] createProfile(String sequence, int alphabetSize) {
+ int length = sequence.length();
+ ProfileCharacter results[] = new ProfileCharacter[length];
+ for (int i = 0; i < length; i++) {
+ ProfileCharacter profile = new ProfileCharacter(alphabetSize);
+ profile.addCharacter(sequence.charAt(i), 1);
+ results[i] = profile;
+ }
+ return results;
+ }
+
+ void addSequence(int sequenceNumber,String sequence) {
+ sequence=sequence.toUpperCase();
+ if (automaticallyCalculatedAlphabetSize)
+ throw new IllegalStateException("if the constructor 'public Profile(int sequenceNumber,String sequence)' is used, it's not safe to add new sequences");
+ if (supportsFreeEndGaps) sequence=supportFreeEndGaps( sequence);
+ sequenceCount++;
+ if (sequenceCount == 1) {
+ profile = createProfile(sequence, alphabetSize);
+ }
+ else {
+ assert(profile.length == sequence.length());
+ for (int i = 0; i < profile.length; i++) {
+ ProfileCharacter character = profile[i];
+ character.addCharacter(sequence.charAt(i), 1);
+ }
+ }
+ paddedSequences.put(sequenceNumber, sequence);
+
+// length = profile.length;
+ }
+
+ public void remove(Profile remove) {
+ int size = length();
+ assert(size == remove.length());
+ for (int i = 0; i < size; i++) {
+ profile[i].removeProfileCharacter(remove.profile[i]);
+ }
+ sequenceCount-= remove.sequenceCount;
+ for (Integer sequenceNumber : remove.paddedSequences.keySet()) {
+ paddedSequences.remove(sequenceNumber);
+ }
+
+ trim();
+
+ }
+
+ /* used after a sequence has been removed from a profile to remove profile characters
+ that are all gap characters in the remaining sequences profiled.
+ */
+ private void trim() {
+ int gapCount = 0;
+ int count = 0;
+ for (ProfileCharacter character : profile) {
+ if(character.isAllGaps ())
+ gapCount ++;
+ else
+ count ++;
+ }
+// System.out.println("gaps =" + gapCount+ "," + count);
+ if(gapCount== 0) return;
+ ProfileCharacter characters[]=new ProfileCharacter[count];
+ char [][] sequences = new char[sequenceCount][];
+ char[][] newSequences = new char[sequenceCount][];
+ int[] sequenceNumbers =new int[sequenceCount];
+ for (int i = 0; i < sequenceCount; i++) {
+ newSequences[i]=new char[count];
+ }
+ int i = 0;
+ for (Map.Entry<Integer, String> entry : paddedSequences.entrySet()) {
+ sequenceNumbers[i]= entry.getKey();
+ sequences[i++] = entry.getValue ().toCharArray();
+ }
+ assert(i== sequenceCount);
+ int index = 0;
+ int sourceIndex = 0;
+ for (ProfileCharacter character : profile) {
+ if (character.isAllGaps()) {
+ sourceIndex++;
+ continue;
+ }
+ characters [ index ] = character;
+ for (int j = 0; j < sequenceCount; j++) {
+ newSequences[j][ index ] = sequences [j][ sourceIndex ];
+ }
+ sourceIndex++;
+ index ++;
+ }
+ for (int j = 0; j < sequenceCount; j++) {
+ String sequence = new String(newSequences[j]);
+ assert(sequence.length()== count);
+ paddedSequences.put(sequenceNumbers[j], sequence);
+ }
+ profile= characters;
+ }
+
+ public static Profile combine(Profile profile1, Profile profile2, AlignmentResult result1, AlignmentResult result2) {
+ int size = result1.size;
+ int alphabetSize = Math.max(profile1.alphabetSize,profile2.alphabetSize);
+ Profile result = new Profile(alphabetSize);
+ result.profile = new ProfileCharacter[size];
+ int index1= 0;
+ int index2= 0;
+ for (int i = 0; i < size; i++) {
+ ProfileCharacter character = new ProfileCharacter(alphabetSize);
+ if(result1.values[i]) {
+ character.addProfileCharacter(profile1.profile[index1++]);
+ }
+ else {
+ character.addGaps(profile1.sequenceCount);
+ }
+ if(result2.values[i]) {
+ character.addProfileCharacter(profile2.profile[index2++]);
+ }
+ else {
+ character.addGaps(profile2.sequenceCount);
+ }
+ result.profile[i]= character;
+ }
+ for (Map.Entry<Integer, String> entry : profile1.paddedSequences.entrySet()) {
+ String sequence = entry.getValue();
+ sequence = buildAlignmentString(sequence, result1);
+ result.paddedSequences.put(entry.getKey(), sequence);
+ }
+ for (Map.Entry<Integer, String> entry : profile2.paddedSequences.entrySet()) {
+ String sequence = entry.getValue();
+ sequence = buildAlignmentString(sequence, result2);
+ result.paddedSequences.put(entry.getKey(), sequence);
+ }
+ result.sequenceCount= profile1.sequenceCount + profile2.sequenceCount;
+ assert(result.sequenceCount == result.paddedSequences.size());
+// result.length = size;
+ return result;
+ }
+
+ public static int calculateAlphabetSize(String[] sequences) {
+ int total = 0;
+ boolean found[] = new boolean[127];
+ for (String sequence : sequences) {
+ for (char character : sequence.toCharArray()) {
+ if(! found [ character ]) total ++;
+ found [ character ] = true;
+ }
+ }
+ return total;
+ }
+
+ public static String buildAlignmentString(String sequence, AlignmentResult result) {
+
+ StringBuilder builder = new StringBuilder();
+// System.out.println("sequence =" + sequence);
+// result.print ();
+// if(true) return "";
+ int index = 0;
+ for (int i = 0; i < result.size; i++) {
+ if(result.values[i]) {
+ builder.append(sequence.charAt(index ++));
+ }
+ else {
+ builder.append('-');
+ }
+ }
+ assert(index == sequence.length());
+ return builder.toString();
+ }
+
+ public void print(boolean displaySequences) {
+ if(displaySequences) {
+ int maximum = 0;
+ for (int k = 0; k < paddedSequences.size(); k++) {
+ String sequence = paddedSequences.get(k);
+ maximum = Math.max(maximum, sequence.length ());
+ System.out.println(sequence);
+ }
+ for (int i = 0; i < maximum; i++) {
+ System.out.print(i % 10);
+ }
+ }
+ System.out.println ();
+ int count = 0;
+ int index = 0;
+ for (ProfileCharacter character : profile) {
+ System.out.print(" " +(index ++) + ":");
+ count +=character.print ();
+ if(count> 800000) {
+ count = 0;
+ System.out.println ();
+ }
+ }
+ System.out.println();
+ }
+
+
+ /**
+ *
+ * @param offset in the range 0 to n - 1
+ * @param count number of characters to include
+ * @return string of the characters starting at offset
+ */
+ public String toString(int offset, int count) {
+ StringBuilder result =new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ ProfileCharacter character = profile[ offset +i];
+ result.append(character.toString());
+ }
+ return result.toString();
+ }
+
+ static String supportFreeEndGaps( String sequence) {
+ char[] characters =sequence.toCharArray();
+ int count = characters.length;
+ int highestNonGapIndex = 0;
+ int lowestNonGapIndex = count;
+ for (int i = 0; i<count;i++) {
+ if ( characters [i] !='-') {
+ lowestNonGapIndex=i;
+ break;
+ }
+ }
+ for (int i = count-1; i >=0 ; i--) {
+ if (characters[i] != '-') {
+ highestNonGapIndex = i;
+ break;
+ }
+ }
+ StringBuilder result =new StringBuilder(count);
+ for (int i = 0; i < count; i++) {
+ if (i<lowestNonGapIndex || i>highestNonGapIndex)
+ result.append('_');
+ else
+ result.append (characters[i]);
+ }
+ return result.toString();
+ }
+
+ /**
+ * Return a copy of this profile that supports free end gaps.
+ * This means that gaps at either end of the sequence are represented as "_" instead of "-".
+ * The score matrix used should have 0 cost for anything compared to "_".
+ * @return a copy of this profile that supports free end gaps or
+ * this profile of that already supports free end gaps.
+ */
+ public Profile supportFreeEndGaps() {
+ if (supportsFreeEndGaps) return this;
+ if (sequenceCount<2) return this;
+ Profile result =new Profile(alphabetSize+1);
+ result.supportsFreeEndGaps=true;
+ for (Map.Entry<Integer, String> entry : paddedSequences.entrySet()) {
+ String sequence =entry.getValue();
+ result.addSequence(entry.getKey(), sequence);
+ }
+ return result;
+ }
+}
diff --git a/src/jebl/evolution/align/ProfileCharacter.java b/src/jebl/evolution/align/ProfileCharacter.java
new file mode 100644
index 0000000..5c95b0d
--- /dev/null
+++ b/src/jebl/evolution/align/ProfileCharacter.java
@@ -0,0 +1,168 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+/**
+ * @author Matt Kearse
+ * @version $Id: ProfileCharacter.java 650 2007-03-12 20:09:10Z twobeers $
+ *
+ * Represents a single residue in a multiple alignment profile
+ */
+public class ProfileCharacter {
+ /*
+ 'characters' contains the actual residue character, and a parallel array called
+ 'count' contains the number of times that character occurs. NumberOfUniqueCharacters
+ contains the length of these to parallel arrays. 'totalCharacters' is the sum of
+ all entries in the array 'count'
+ */
+ private char characters[];
+ private int count[];
+ private int numberOfUniqueCharacters;
+ private int totalCharacters;
+ private boolean calculatedGapFraction=false;
+ private float gapFraction;
+
+ public ProfileCharacter(int alphabetSize) {
+ characters = new char[alphabetSize +1];
+ count = new int[alphabetSize +1];
+ }
+
+ public void addCharacter(char character, int increment) {
+ calculatedGapFraction = false;
+ totalCharacters += increment;
+ for (int i = 0; i < numberOfUniqueCharacters; i++) {
+ if(characters[i]== character) {
+ count[i]+= increment;
+ return;
+ }
+ }
+ characters [ numberOfUniqueCharacters ] = character;
+ count [ numberOfUniqueCharacters ++ ] = increment;
+ }
+
+ private void removeCharacter(char character, int increment) {
+ calculatedGapFraction = false;
+ totalCharacters -= increment;
+ for (int i = 0; i < numberOfUniqueCharacters; i++) {
+ if (characters[i] == character) {
+ count[i] -= increment;
+ if(count[i]== 0) {
+ count[i]= count [ numberOfUniqueCharacters -1];
+ characters[i]= characters [ numberOfUniqueCharacters - 1];
+ numberOfUniqueCharacters --;
+ }
+ return;
+ }
+ }
+ assert(false);
+ }
+
+ public void addProfileCharacter(ProfileCharacter character) {
+ for (int j = 0; j < character.numberOfUniqueCharacters; j++) {
+ addCharacter(character.characters[j], character.count[j]);
+ }
+ }
+
+ public void removeProfileCharacter(ProfileCharacter character) {
+ for (int j = 0; j < character.numberOfUniqueCharacters; j++) {
+ removeCharacter(character.characters[j], character.count[j]);
+ }
+
+ }
+
+ public void addGaps(int count) {
+ addCharacter('-', count);
+ }
+
+ public static float score(ProfileCharacter character1, ProfileCharacter character2, Scores scores) {
+ float score = 0;
+ int totalCharacters = character1.totalCharacters*character2.totalCharacters;
+ if(totalCharacters == 1) {
+ return scores.score
+ [ character1.characters [0]]
+ [ character2.characters [0]];
+ }
+ for (int i = 0; i < character1.numberOfUniqueCharacters; i++) {
+ for (int j = 0; j < character2.numberOfUniqueCharacters; j++) {
+ score += scores.score [character1.characters[i]] [ character2.characters[j]]*
+ character1.count[i]*character2.count[j];
+ }
+ }
+ return score/totalCharacters;
+ }
+
+ public static float scoreSelf(ProfileCharacter character, Scores scores) {
+ float score = 0;
+ long totalCharacters = ((long)character.totalCharacters) * character.totalCharacters;
+ if (totalCharacters == 1) {
+ return scores.score[character.characters[0]][character.characters[0]];
+ }
+ for (int i = 0; i < character.numberOfUniqueCharacters; i++) {
+ for (int j = 0; j < character.numberOfUniqueCharacters; j++) {
+ score += scores.score[character.characters[i]][character.characters[j]] *
+ character.count[i] * character.count[j];
+ }
+ }
+
+ //reduce counts of identical characters being compared by one
+ // for example, if comparing A:1 and B:1, score should be minimum
+ // but if comparing A:2 B:1, score should be higher
+ for (int i = 0; i < character.numberOfUniqueCharacters; i++) {
+ score -= scores.score[character.characters[i]][character.characters[i]];
+ totalCharacters --;
+ }
+
+ return score / totalCharacters;
+ }
+
+
+ public int print() {
+ System.out.print(toString());
+ return numberOfUniqueCharacters;
+ }
+
+ public String toString() {
+ if(numberOfUniqueCharacters==1) {
+ return "" +characters[0];
+ }
+ StringBuilder result =new StringBuilder();
+ result.append("(");
+ for (int i = 0; i < numberOfUniqueCharacters; i++) {
+ result.append(String.format("%c: %d ", characters[i], count[i]));
+ }
+ result.append(")");
+ return result.toString();
+ }
+
+ public boolean isAllGaps() {
+ if(numberOfUniqueCharacters > 2) return false;
+ if(characters[0]!='-' && characters[0]!='_') return false;
+ if (numberOfUniqueCharacters==1) return true;
+ if (characters[1] != '-' && characters[1] != '_') return false;
+ return true;
+
+ }
+
+ public void clear() {
+ numberOfUniqueCharacters= 0;
+ totalCharacters= 0;
+ }
+
+ /**
+ *
+ * @return the fraction of characters that are gap Characters in this profile
+ */
+ public float gapFraction () {
+ if (totalCharacters==0) return 0;
+ if (calculatedGapFraction) return gapFraction;
+ int gapCount=0;
+ for (int i = 0; i < numberOfUniqueCharacters; i++) {
+ if(characters[i]=='-' || characters[i]=='_') {
+ gapCount+=count[i];
+ }
+ }
+ gapFraction = ((float) gapCount) / totalCharacters;
+ assert gapFraction >= 0;
+ return gapFraction;
+ }
+}
diff --git a/src/jebl/evolution/align/SequenceAlignmentsDistanceMatrix.java b/src/jebl/evolution/align/SequenceAlignmentsDistanceMatrix.java
new file mode 100644
index 0000000..ae3a293
--- /dev/null
+++ b/src/jebl/evolution/align/SequenceAlignmentsDistanceMatrix.java
@@ -0,0 +1,61 @@
+package jebl.evolution.align;
+
+import jebl.evolution.distances.BasicDistanceMatrix;
+import jebl.evolution.distances.F84DistanceMatrix;
+import jebl.evolution.distances.JukesCantorDistanceMatrix;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.ProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * Builds a distance matrix by performing a series of pairwise alignments between the
+ * specified sequences (unlike the methods in jebl.evolution.distances, which
+ * extract the pairwise distances from a multiple sequence alignment).
+ *
+ * @author Joseph Heled
+ * @version $Id: SequenceAlignmentsDistanceMatrix.java 660 2007-03-20 03:41:45Z twobeers $
+ *
+ */
+public class SequenceAlignmentsDistanceMatrix extends BasicDistanceMatrix {
+ public SequenceAlignmentsDistanceMatrix(List<Sequence> seqs, PairwiseAligner aligner, ProgressListener progress) {
+ super(getTaxa(seqs), getDistances(seqs, aligner, progress));
+ }
+
+ static List<Taxon> getTaxa(List<Sequence> seqs) {
+ List<Taxon> t = new ArrayList<Taxon>();
+ for( Sequence s : seqs ) {
+ t.add(s.getTaxon());
+ }
+ return t;
+ }
+
+
+
+ static double[][] getDistances(List<Sequence> seqs, PairwiseAligner aligner, final ProgressListener progress) {
+ final int n = seqs.size();
+ double [][] d = new double[n][n];
+ boolean isProtein = seqs.get(0).getSequenceType().getCanonicalStateCount()> 4;
+
+ CompoundAlignmentProgressListener compoundProgress = new CompoundAlignmentProgressListener(progress,(n * (n - 1)) / 2);
+
+ for(int i = 0; i < n; ++i) {
+ for(int j = i+1; j < n; ++j) {
+ PairwiseAligner.Result result = aligner.doAlignment(seqs.get(i), seqs.get(j), compoundProgress.getMinorProgress());
+ compoundProgress.incrementSectionsCompleted(1);
+ if(compoundProgress.isCanceled()) return d;
+ if(isProtein) {
+ d[i][j] = new JukesCantorDistanceMatrix(result.alignment, null).getDistances()[0][1];
+ } else {
+ d[i][j] = new F84DistanceMatrix(result.alignment).getDistances()[0][1];
+
+ }
+ d[j][i] = d[i][j];
+ }
+ }
+ return d;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/SequenceShuffler.java b/src/jebl/evolution/align/SequenceShuffler.java
new file mode 100644
index 0000000..ad977d7
--- /dev/null
+++ b/src/jebl/evolution/align/SequenceShuffler.java
@@ -0,0 +1,112 @@
+package jebl.evolution.align;
+
+import javax.swing.*;
+import java.util.Random;
+
+/**
+ * Shuffles a sequence and aligns it again multiple times to give mean and variance of
+ * alignments on random sequences.
+ *
+ * @author Richard Moir
+ * @author Alexei Drummond
+ *
+ * @version $Id: SequenceShuffler.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class SequenceShuffler {
+
+ private float max, min;
+ private float mean; //mean score for the set of shuffled alignments
+ private float variance; //variance of the scores for the set of shuffled alignments
+
+ private ProgressMonitor monitor = null;
+
+ public SequenceShuffler() {}
+
+ public void shuffle(Align algorithm, String sq1, String sq2, final int numShuffles) {
+
+ if (monitor != null) {
+ monitor.setMinimum(0);
+ monitor.setMaximum(numShuffles);
+ }
+
+ float[] scores = new float[numShuffles];
+ float sumScores = 0;
+ for(int i = 0; i < numShuffles; i++) {
+ String shuffled2 = shuffleSeq(sq2);
+ algorithm.doAlignment(sq1,shuffled2);
+ scores[i] = algorithm.getScore();
+ sumScores += algorithm.getScore();
+ final int j = i;
+ if (monitor != null) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ monitor.setProgress(j);
+ }
+ };
+ SwingUtilities.invokeLater(runnable);
+ }
+ }
+ if (monitor != null) {
+ monitor.setProgress(monitor.getMaximum());
+ }
+
+ mean = sumScores / numShuffles;
+ min = Float.MAX_VALUE;
+ max = -Float.MAX_VALUE;
+ float sqDiffSum = 0;
+ for(int i = 0; i < numShuffles; i++) {
+ sqDiffSum += Math.pow(scores[i] - mean, 2);
+ if (scores[i] < min) min = scores[i];
+ if (scores[i] > max) max = scores[i];
+ }
+ variance = sqDiffSum / numShuffles;
+ }
+
+ /**
+ * Note: not to sure how good this shuffling algorithm is.
+ *
+ * @param s string to shuffle
+ * @return shuffled string
+ */
+ private String shuffleSeq(String s) {
+ char[] a = s.toCharArray();
+ char swap;
+ for (int i = 0; i < a.length-1; i++) {
+ int r = random.nextInt(a.length-i-1) + 1;
+ swap = a[r];
+ a[r] = a[i];
+ a[i] = swap;
+ }
+ return String.valueOf(a);
+ }
+
+ /**
+ *
+ * @return the mean score of the shuffled alignments.
+ */
+ public float getMean() {
+ return mean;
+ }
+
+ public float getMax() {
+ return max;
+ }
+
+ public float getMin() {
+ return min;
+ }
+
+ /**
+ *
+ * @return the standard deviation of scores for the shuffled alignments.
+ */
+ public double getStdev() {
+ return Math.sqrt(variance);
+ }
+
+ public void setProgressMonitor(ProgressMonitor monitor) {
+ this.monitor = monitor;
+ }
+
+ private static Random random = new Random();
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/SmithWaterman.java b/src/jebl/evolution/align/SmithWaterman.java
new file mode 100644
index 0000000..2f87ddd
--- /dev/null
+++ b/src/jebl/evolution/align/SmithWaterman.java
@@ -0,0 +1,56 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+import jebl.util.ProgressListener;
+
+public class SmithWaterman extends AlignSimple {
+
+ public SmithWaterman(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ * @param progress
+ */
+ public void doAlignment(String sq1, String sq2, ProgressListener progress) {
+
+ super.prepareAlignment(sq1, sq2);
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ int maxi = n, maxj = m;
+ float maxval = Float.NEGATIVE_INFINITY;
+ for (int i=1; i<=n; i++) {
+ for (int j=1; j<=m; j++) {
+ float s = score[s1[i-1]][s2[j-1]];
+ float val = max(0, F[i-1][j-1]+s, F[i-1][j]-d, F[i][j-1]-d);
+ F[i][j] = val;
+ if (val == 0) {
+ B[i][j].setTraceback(-1,-1);
+ } else if (val == F[i-1][j-1]+s) {
+ B[i][j].setTraceback(i-1, j-1);
+ } else if (val == F[i-1][j]-d) {
+ B[i][j].setTraceback(i-1, j);
+ } else if (val == F[i][j-1]-d) {
+ B[i][j].setTraceback(i, j-1);
+ } else {
+ throw new Error("Error in SmithWaterman alignment.");
+ }
+ if (val > maxval) {
+ maxval = val;
+ maxi = i; maxj = j;
+ }
+ }
+ }
+ B0 = new TracebackSimple(maxi, maxj);
+ }
+
+ public void doAlignment(String sq1, String sq2) {
+ doAlignment(sq1, sq2, null);
+ }
+}
diff --git a/src/jebl/evolution/align/SmithWatermanLinearSpace.java b/src/jebl/evolution/align/SmithWatermanLinearSpace.java
new file mode 100644
index 0000000..a12cac5
--- /dev/null
+++ b/src/jebl/evolution/align/SmithWatermanLinearSpace.java
@@ -0,0 +1,97 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+
+public class SmithWatermanLinearSpace extends AlignLinearSpace {
+
+ TracebackSimple[][] start = null; // Best alignment ending at (i,j) begins at start[i][j]
+ float maxval; // Score of best alignment
+ int start1, start2; // Best alignment begins at (start1, start2)
+ int end1, end2; // Best alignment ends at (end1, end2)
+
+ public SmithWatermanLinearSpace(Scores sub, float d) {
+ super(sub, d);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2) {
+
+ super.prepareAlignment(sq1, sq2);
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ start = new TracebackSimple[2][sq2.length() + 1];
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ maxval = Float.NEGATIVE_INFINITY;
+ // Initialize first column (i=0):
+ for (int j=0; j<=m; j++) {
+ start[1][j] = new TracebackSimple(0, j);
+ }
+ for (int i=1; i<=n; i++) {
+ swap01(F);
+ swap01(start);
+ // F[1] represents (new) column i and F[0] represents (old) column i-1
+ // Initialize first row (j=0):
+ start[1][0] = new TracebackSimple(i, 0);
+ for (int j=1; j<=m; j++) {
+ float s = score[s1[i-1]][s2[j-1]];
+ float val = max(0, F[0][j-1]+s, F[0][j]-d, F[1][j-1]-d);
+ F[1][j] = val;
+ if (val == 0) { // Best alignment starts (and ends) here
+ start[1][j] = new TracebackSimple(i, j);
+ } else if (val == F[0][j-1]+s) {
+ start[1][j] = start[0][j-1];
+ } else if (val == F[0][j]-d) {
+ start[1][j] = start[0][j];
+ } else if (val == F[1][j-1]-d) {
+ start[1][j] = start[1][j-1];
+ } else {
+ throw new Error("SWSmart 1");
+ }
+ if (val > maxval) {
+ maxval = val;
+ TracebackSimple sij = start[1][j];
+ start1 = sij.i; start2 = sij.j;
+ end1 = i; end2 = j;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return maxval; }
+
+ /**
+ * @return two-element array containing an alignment with maximal score
+ */
+ public String[] getMatch() {
+ String subseq1 = seq1.substring(start1, end1);
+ String subseq2 = seq2.substring(start2, end2);
+ // The optimal local alignment between seq1 and seq2 is the
+ // optimal global alignment between subseq1 and subseq2:
+ NeedlemanWunschLinearSpace nwls1 = new NeedlemanWunschLinearSpace(sub, d);
+ nwls1.doAlignment(subseq1, subseq2);
+ return nwls1.getMatch();
+ }
+
+ public void traceback(TracebackPlotter plotter) {
+ String subseq1 = seq1.substring(start1, end1);
+ String subseq2 = seq2.substring(start2, end2);
+ // The optimal local alignment between seq1 and seq2 is the
+ // optimal global alignment between subseq1 and subseq2:
+ NeedlemanWunschLinearSpace nwls1 = new NeedlemanWunschLinearSpace(sub, d);
+ nwls1.doAlignment(subseq1, subseq2);
+ nwls1.traceback(plotter, start1, start2, seq1, seq2);
+ }
+
+
+
+}
diff --git a/src/jebl/evolution/align/SmithWatermanLinearSpaceAffine.java b/src/jebl/evolution/align/SmithWatermanLinearSpaceAffine.java
new file mode 100644
index 0000000..62b6149
--- /dev/null
+++ b/src/jebl/evolution/align/SmithWatermanLinearSpaceAffine.java
@@ -0,0 +1,121 @@
+package jebl.evolution.align;
+
+import jebl.evolution.align.scores.Scores;
+import jebl.util.ProgressListener;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: SmithWatermanLinearSpaceAffine.java 384 2006-07-17 07:17:39Z pepster $
+ */
+public class SmithWatermanLinearSpaceAffine extends AlignLinearSpaceAffine {
+
+ TracebackSimple[][] start; // Best alignment ending at (i,j) begins at start[i][j]
+ float maxval; // Score of best alignment
+ int start1, start2; // Best alignment begins at (start1, start2)
+ int end1, end2; // Best alignment ends at (end1, end2)
+
+ public SmithWatermanLinearSpaceAffine(Scores sub, float d, float e) {
+ super(sub, d, e);
+ }
+
+ /**
+ * @param sq1
+ * @param sq2
+ */
+ public void doAlignment(String sq1, String sq2, ProgressListener progress) {
+
+ prepareAlignment(sq1, sq2);
+
+ char[] s1 = sq1.toCharArray();
+ char[] s2 = sq2.toCharArray();
+
+ int n = this.n, m = this.m;
+ float[][] score = sub.score;
+ float[][] M = F[0], Ix = F[1], Iy = F[2];
+ start = new TracebackSimple[2][m+1];
+ maxval = Float.NEGATIVE_INFINITY;
+ // Initialize first column (i=0); score is zero:
+ for (int j=0; j<=m; j++) {
+ start[1][j] = new TracebackSimple(0, j);
+ }
+
+
+ for (int i=1; i<=n; i++) {
+ if( progress != null && progress.setProgress((double)i/n) ) {
+ return;
+ }
+
+ swap01(M); swap01(Ix); swap01(Iy); swap01(start);
+ // F[k][1] represents (new) col i and F[k][0] represents (old) col i-1
+ // Initialize first row (j=0):
+ start[1][0] = new TracebackSimple(i, 0);
+ for (int j=1; j<=m; j++) {
+ float s = score[s1[i-1]][s2[j-1]];
+ float val, valm, valix, valiy;
+ valm = M[1][j] = max(0, M[0][j-1]+s, Ix[0][j-1]+s, Iy[0][j-1]+s);
+ valix = Ix[1][j] = max(M[0][j]-d, Ix[0][j]-e);
+ valiy = Iy[1][j] = max(M[1][j-1]-d, Iy[1][j-1]-e);
+
+ val = max(valm, valix, valiy);
+ if (val == 0) {
+ start[1][j] = new TracebackSimple(i, j);
+ } else if (val == valm) {
+ start[1][j] = start[0][j-1];
+ } else if (val == valix) {
+ start[1][j] = start[0][j];
+ } else if (val == valiy) {
+ start[1][j] = start[1][j-1];
+ } else {
+ throw new Error("SWSmartAffine 1");
+ }
+ if (val > maxval) {
+ maxval = val;
+ TracebackSimple sij = start[1][j];
+ start1 = sij.i; start2 = sij.j;
+ end1 = i; end2 = j;
+ }
+ }
+ }
+ }
+
+
+ public void doAlignment(String sequence1, String sequence2) {
+ doAlignment(sequence1, sequence2, null);
+ }
+
+ /**
+ * @return the score of the best alignment
+ */
+ public float getScore() { return maxval; }
+
+ /**
+ * @return two-element array containing an alignment with maximal score
+ */
+ public String[] getMatch() {
+
+ String subseq1 = seq1.substring(start1, end1);
+ String subseq2 = seq2.substring(start2, end2);
+
+ // The optimal local alignment between seq1 and seq2 is the
+ // optimal global alignment between subseq1 and subseq2:
+ NeedlemanWunschLinearSpaceAffine nwa1 = new NeedlemanWunschLinearSpaceAffine(sub, d, e);
+ nwa1.doAlignment(subseq1, subseq2);
+ return nwa1.getMatch();
+ }
+
+ public void traceback(TracebackPlotter plotter) {
+
+ String subseq1 = seq1.substring(start1, end1);
+ String subseq2 = seq2.substring(start2, end2);
+
+ // The optimal local alignment between seq1 and seq2 is the
+ // optimal global alignment between subseq1 and subseq2:
+ NeedlemanWunschAffine nwa1 = new NeedlemanWunschAffine(sub, d, e);
+ nwa1.doAlignment(subseq1, subseq2);
+
+ nwa1.traceback(plotter);
+ }
+
+}
+
diff --git a/src/jebl/evolution/align/SystemOut.java b/src/jebl/evolution/align/SystemOut.java
new file mode 100644
index 0000000..394c52b
--- /dev/null
+++ b/src/jebl/evolution/align/SystemOut.java
@@ -0,0 +1,8 @@
+package jebl.evolution.align;
+
+class SystemOut extends Output {
+
+ public final void print(String s) { System.out.print(s); }
+ public final void println(String s) { System.out.println(s); }
+ public final void println() { System.out.println(); }
+}
diff --git a/src/jebl/evolution/align/Traceback.java b/src/jebl/evolution/align/Traceback.java
new file mode 100644
index 0000000..6037494
--- /dev/null
+++ b/src/jebl/evolution/align/Traceback.java
@@ -0,0 +1,12 @@
+package jebl.evolution.align;
+
+public abstract class Traceback {
+
+ int i, j; // absolute coordinates
+
+ public final int getX() { return i; }
+
+ public final int getY() { return j; }
+
+ public String toString() { return "("+getX() + ", " + getY()+")";};
+}
diff --git a/src/jebl/evolution/align/TracebackAffine.java b/src/jebl/evolution/align/TracebackAffine.java
new file mode 100644
index 0000000..897eca0
--- /dev/null
+++ b/src/jebl/evolution/align/TracebackAffine.java
@@ -0,0 +1,19 @@
+package jebl.evolution.align;
+
+class TracebackAffine extends Traceback {
+
+ int k;
+
+ public TracebackAffine(int k, int i, int j) {
+
+ this.k = k;
+ this.i = i;
+ this.j = j;
+ }
+
+ public final void setTraceback(int k, int i, int j) {
+ this.i = i;
+ this.j = j;
+ this.k = k;
+ }
+}
diff --git a/src/jebl/evolution/align/TracebackPlotter.java b/src/jebl/evolution/align/TracebackPlotter.java
new file mode 100644
index 0000000..c62ad62
--- /dev/null
+++ b/src/jebl/evolution/align/TracebackPlotter.java
@@ -0,0 +1,17 @@
+package jebl.evolution.align;
+
+/**
+ *
+ * @author Alexei Drummond
+ *
+ * @version $Id: TracebackPlotter.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ */
+public interface TracebackPlotter {
+
+ void newTraceBack(String sequence1, String sequence2);
+
+ void traceBack(Traceback t);
+
+ void finishedTraceBack();
+}
diff --git a/src/jebl/evolution/align/TracebackSimple.java b/src/jebl/evolution/align/TracebackSimple.java
new file mode 100644
index 0000000..b551297
--- /dev/null
+++ b/src/jebl/evolution/align/TracebackSimple.java
@@ -0,0 +1,13 @@
+package jebl.evolution.align;
+
+class TracebackSimple extends Traceback {
+
+ public TracebackSimple(int i, int j) {
+ this.i = i; this.j = j;
+ }
+
+ public final void setTraceback(int i, int j) {
+ this.i = i;
+ this.j = j;
+ }
+}
diff --git a/src/jebl/evolution/align/package.html b/src/jebl/evolution/align/package.html
new file mode 100644
index 0000000..3bf2994
--- /dev/null
+++ b/src/jebl/evolution/align/package.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ <title>jebl.evolution.align package</title>
+</head>
+<body bgcolor="white">
+
+Provides classes and interfaces for pairwise alignment of two sequences.
+All major algorithms including Smith-Waterman and Needleman-Wunsch
+are implemented.
+<p>
+Some of the classes in this package are based on source code by Peter Sestoft, sestoft at dina.kvl.dk.
+His original source code can be at
+<a href="http://www.dina.kvl.dk/~sestoft/bsa.html">http://www.dina.kvl.dk/~sestoft/bsa.html</a>
+</body>
+</html>
diff --git a/src/jebl/evolution/align/scores/AminoAcidScores.java b/src/jebl/evolution/align/scores/AminoAcidScores.java
new file mode 100644
index 0000000..92cc3af
--- /dev/null
+++ b/src/jebl/evolution/align/scores/AminoAcidScores.java
@@ -0,0 +1,25 @@
+package jebl.evolution.align.scores;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: AminoAcidScores.java 360 2006-06-22 07:42:48Z pepster $
+ */
+public class AminoAcidScores extends Scores {
+
+ private String residues = "ARNDCQEGHILKMFPSTWYV";
+
+ public String getName() {
+ return toString();
+ }
+
+ public final String getAlphabet() { return residues + getExtraResidues(); }
+
+
+ public AminoAcidScores() {
+ }
+
+ public AminoAcidScores(float m, float n) {
+ buildScores(m, n);
+ }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum45.java b/src/jebl/evolution/align/scores/Blosum45.java
new file mode 100644
index 0000000..8d0412f
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum45.java
@@ -0,0 +1,32 @@
+package jebl.evolution.align.scores;
+
+public class Blosum45 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 5},
+ { -2, 7},
+ { -1, 0, 6},
+ { -2, -1, 2, 7},
+ { -1, -3, -2, -3, 12},
+ { -1, 1, 0, 0, -3, 6},
+ { -1, 0, 0, 2, -3, 2, 6},
+ { 0, -2, 0, -1, -3, -2, -2, 7},
+ { -2, 0, 1, 0, -3, 1, 0, -2, 10},
+ { -1, -3, -2, -4, -3, -2, -3, -4, -3, 5},
+ { -1, -2, -3, -3, -2, -2, -2, -3, -2, 2, 5},
+ { -1, 3, 0, 0, -3, 1, 1, -2, -1, -3, -3, 5},
+ { -1, -1, -2, -3, -2, 0, -2, -2, 0, 2, 2, -1, 6},
+ { -2, -2, -2, -4, -2, -4, -3, -3, -2, 0, 1, -3, 0, 8},
+ { -1, -2, -2, -1, -4, -1, 0, -2, -2, -2, -3, -1, -2, -3, 9},
+ { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -3, -1, -2, -2, -1, 4},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -1, -1, 2, 5},
+ { -2, -2, -4, -4, -5, -2, -3, -2, -3, -2, -2, -2, -2, 1, -3, -4, -3, 15},
+ { -2, -1, -2, -2, -3, -1, -2, -3, 2, 0, 0, -1, 0, 3, -3, -2, -1, 3, 8},
+ { 0, -2, -3, -3, -1, -3, -3, -3, -3, 3, 1, -2, 1, 0, -3, -1, 0, -3, -1, 5}};
+
+ public Blosum45() { buildScores(residueScores); }
+
+ public String getName() { return "Blosum45"; }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum50.java b/src/jebl/evolution/align/scores/Blosum50.java
new file mode 100644
index 0000000..960b8bd
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum50.java
@@ -0,0 +1,35 @@
+package jebl.evolution.align.scores;
+
+public class Blosum50 extends AminoAcidScores {
+
+
+ private final float[][] residueScores =
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { /* A */ { 5 },
+ /* R */ { -2, 7 },
+ /* N */ { -1,-1, 7 },
+ /* D */ { -2,-2, 2, 8 },
+ /* C */ { -1,-4,-2,-4,13 },
+ /* Q */ { -1, 1, 0, 0,-3, 7 },
+ /* E */ { -1, 0, 0, 2,-3, 2, 6 },
+ /* G */ { 0,-3, 0,-1,-3,-2,-3, 8 },
+ /* H */ { -2, 0, 1,-1,-3, 1, 0,-2,10 },
+ /* I */ { -1,-4,-3,-4,-2,-3,-4,-4,-4, 5 },
+ /* L */ { -2,-3,-4,-4,-2,-2,-3,-4,-3, 2, 5 },
+ /* K */ { -1, 3, 0,-1,-3, 2, 1,-2, 0,-3,-3, 6 },
+ /* M */ { -1,-2,-2,-4,-2, 0,-2,-3,-1, 2, 3,-2, 7 },
+ /* F */ { -3,-3,-4,-5,-2,-4,-3,-4,-1, 0, 1,-4, 0, 8 },
+ /* P */ { -1,-3,-2,-1,-4,-1,-1,-2,-2,-3,-4,-1,-3,-4,10 },
+ /* S */ { 1,-1, 1, 0,-1, 0,-1, 0,-1,-3,-3, 0,-2,-3,-1, 5 },
+ /* T */ { 0,-1, 0,-1,-1,-1,-1,-2,-2,-1,-1,-1,-1,-2,-1, 2, 5 },
+ /* W */ { -3,-3,-4,-5,-5,-1,-3,-3,-3,-3,-2,-3,-1, 1,-4,-4,-3,15 },
+ /* Y */ { -2,-1,-2,-3,-3,-1,-2,-3, 2,-1,-1,-2, 0, 4,-3,-2,-2, 2, 8 },
+ /* V */ { 0,-3,-3,-4,-1,-3,-3,-4,-4, 4, 1,-3, 1,-1,-3,-2, 0,-3,-1, 5 }
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ };
+
+ public Blosum50() { buildScores(residueScores); }
+
+ public String getName() { return "Blosum50"; }
+
+}
diff --git a/src/jebl/evolution/align/scores/Blosum55.java b/src/jebl/evolution/align/scores/Blosum55.java
new file mode 100644
index 0000000..1400961
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum55.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum55 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 5},
+ { -2, 8},
+ { -2, -1, 8},
+ { -2, -2, 2, 8},
+ { 0, -4, -3, -4, 13},
+ { -1, 1, 0, 0, -4, 7},
+ { -1, 0, 0, 2, -4, 2, 7},
+ { 0, -3, 0, -2, -3, -2, -3, 8},
+ { -2, 0, 1, -1, -4, 1, -1, -2, 11},
+ { -2, -4, -4, -4, -2, -4, -4, -5, -4, 6},
+ { -2, -3, -4, -5, -2, -3, -4, -5, -3, 2, 6},
+ { -1, 3, 0, -1, -4, 2, 1, -2, 0, -4, -3, 6},
+ { -1, -2, -3, -4, -2, 0, -3, -3, -2, 2, 3, -2, 8},
+ { -3, -3, -4, -5, -3, -4, -4, -4, -1, 0, 1, -4, 0, 9},
+ { -1, -3, -2, -2, -3, -1, -1, -3, -3, -3, -4, -1, -3, -5, 10},
+ { 2, -1, 1, 0, -1, 0, 0, 0, -1, -3, -3, 0, -2, -3, -1, 5},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -2, -1, -1, -3, -1, 2, 6},
+ { -4, -3, -5, -5, -4, -2, -3, -3, -3, -3, -3, -4, -2, 2, -5, -4, -3, 15},
+ { -2, -2, -2, -3, -3, -1, -2, -4, 2, -1, -1, -2, -1, 4, -4, -2, -2, 3, 9},
+ { 0, -3, -4, -4, -1, -3, -3, -4, -4, 4, 1, -3, 1, -1, -3, -2, 0, -4, -2, 5}};
+
+ public Blosum55() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum60.java b/src/jebl/evolution/align/scores/Blosum60.java
new file mode 100644
index 0000000..d5092f7
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum60.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum60 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -1, 5},
+ { -1, 0, 6},
+ { -2, -1, 1, 6},
+ { 0, -3, -2, -3, 9},
+ { -1, 1, 0, 0, -3, 5},
+ { -1, 0, 0, 2, -3, 2, 5},
+ { 0, -2, 0, -1, -2, -2, -2, 6},
+ { -2, 0, 1, -1, -3, 1, 0, -2, 7},
+ { -1, -3, -3, -3, -1, -3, -3, -3, -3, 4},
+ { -1, -2, -3, -3, -1, -2, -3, -4, -3, 2, 4},
+ { -1, 2, 0, -1, -3, 1, 1, -1, -1, -3, -2, 4},
+ { -1, -1, -2, -3, -1, 0, -2, -2, -1, 1, 2, -1, 5},
+ { -2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6},
+ { -1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7},
+ { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 4},
+ { -3, -3, -4, -4, -2, -2, -3, -2, -2, -2, -2, -3, -1, 1, -4, -3, -2, 10},
+ { -2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 6},
+ { 0, -2, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3, -1, 4}};
+
+ public Blosum60() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum62.java b/src/jebl/evolution/align/scores/Blosum62.java
new file mode 100644
index 0000000..6924043
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum62.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum62 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -1, 5},
+ { -2, 0, 6},
+ { -2, -2, 1, 6},
+ { 0, -3, -3, -3, 9},
+ { -1, 1, 0, 0, -3, 5},
+ { -1, 0, 0, 2, -4, 2, 5},
+ { 0, -2, 0, -1, -3, -2, -2, 6},
+ { -2, 0, 1, -1, -3, 0, 0, -2, 8},
+ { -1, -3, -3, -3, -1, -3, -3, -4, -3, 4},
+ { -1, -2, -3, -4, -1, -2, -3, -4, -3, 2, 4},
+ { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -2, 5},
+ { -1, -1, -2, -3, -1, 0, -2, -3, -2, 1, 2, -1, 5},
+ { -2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6},
+ { -1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7},
+ { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 5},
+ { -3, -3, -4, -4, -2, -2, -3, -2, -2, -3, -2, -3, -1, 1, -4, -3, -2, 11},
+ { -2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 7},
+ { 0, -3, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3, -1, 4}};
+
+ public Blosum62() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum65.java b/src/jebl/evolution/align/scores/Blosum65.java
new file mode 100644
index 0000000..2719f66
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum65.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum65 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -1, 6},
+ { -2, 0, 6},
+ { -2, -2, 1, 6},
+ { 0, -4, -3, -4, 9},
+ { -1, 1, 0, 0, -3, 6},
+ { -1, 0, 0, 2, -4, 2, 5},
+ { 0, -2, -1, -1, -3, -2, -2, 6},
+ { -2, 0, 1, -1, -3, 1, 0, -2, 8},
+ { -1, -3, -3, -3, -1, -3, -3, -4, -3, 4},
+ { -2, -2, -4, -4, -1, -2, -3, -4, -3, 2, 4},
+ { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -3, 5},
+ { -1, -2, -2, -3, -2, 0, -2, -3, -2, 1, 2, -2, 6},
+ { -2, -3, -3, -4, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6},
+ { -1, -2, -2, -2, -3, -1, -1, -2, -2, -3, -3, -1, -3, -4, 8},
+ { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -3, 0, -2, -2, -1, 4},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 5},
+ { -3, -3, -4, -5, -2, -2, -3, -3, -2, -2, -2, -3, -2, 1, -4, -3, -3, 10},
+ { -2, -2, -2, -3, -2, -2, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 7},
+ { 0, -3, -3, -3, -1, -2, -3, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3, -1, 4}};
+
+ public Blosum65() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum70.java b/src/jebl/evolution/align/scores/Blosum70.java
new file mode 100644
index 0000000..a3e616a
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum70.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum70 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -2, 6},
+ { -2, -1, 6},
+ { -2, -2, 1, 6},
+ { -1, -4, -3, -4, 9},
+ { -1, 1, 0, -1, -3, 6},
+ { -1, 0, 0, 1, -4, 2, 5},
+ { 0, -3, -1, -2, -3, -2, -2, 6},
+ { -2, 0, 0, -1, -4, 1, 0, -2, 8},
+ { -2, -3, -4, -4, -1, -3, -4, -4, -4, 4},
+ { -2, -3, -4, -4, -2, -2, -3, -4, -3, 2, 4},
+ { -1, 2, 0, -1, -4, 1, 1, -2, -1, -3, -3, 5},
+ { -1, -2, -2, -3, -2, 0, -2, -3, -2, 1, 2, -2, 6},
+ { -2, -3, -3, -4, -2, -3, -4, -4, -1, 0, 0, -3, 0, 6},
+ { -1, -2, -2, -2, -3, -2, -1, -3, -2, -3, -3, -1, -3, -4, 8},
+ { 1, -1, 0, 0, -1, 0, 0, -1, -1, -3, -3, 0, -2, -3, -1, 4},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -2, -1, -1, -2, -1, 1, 5},
+ { -3, -3, -4, -5, -3, -2, -4, -3, -2, -3, -2, -3, -2, 1, -4, -3, -3, 11},
+ { -2, -2, -2, -4, -3, -2, -3, -4, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 7},
+ { 0, -3, -3, -4, -1, -2, -3, -4, -3, 3, 1, -3, 1, -1, -3, -2, 0, -3, -2, 4}};
+
+ public Blosum70() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum75.java b/src/jebl/evolution/align/scores/Blosum75.java
new file mode 100644
index 0000000..c9729f0
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum75.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum75 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -2, 6},
+ { -2, -1, 6},
+ { -2, -2, 1, 6},
+ { -1, -4, -3, -4, 9},
+ { -1, 1, 0, -1, -3, 6},
+ { -1, 0, -1, 1, -5, 2, 5},
+ { 0, -3, -1, -2, -3, -2, -3, 6},
+ { -2, 0, 0, -1, -4, 1, 0, -2, 8},
+ { -2, -3, -4, -4, -1, -3, -4, -5, -4, 4},
+ { -2, -3, -4, -4, -2, -3, -4, -4, -3, 1, 4},
+ { -1, 2, 0, -1, -4, 1, 1, -2, -1, -3, -3, 5},
+ { -1, -2, -3, -4, -2, 0, -2, -3, -2, 1, 2, -2, 6},
+ { -3, -3, -4, -4, -2, -4, -4, -4, -2, 0, 0, -4, 0, 6},
+ { -1, -2, -3, -2, -4, -2, -1, -3, -2, -3, -3, -1, -3, -4, 8},
+ { 1, -1, 0, -1, -1, 0, 0, -1, -1, -3, -3, 0, -2, -3, -1, 5},
+ { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -2, -1, -1, -2, -1, 1, 5},
+ { -3, -3, -4, -5, -3, -2, -4, -3, -2, -3, -2, -4, -2, 1, -5, -3, -3, 11},
+ { -2, -2, -3, -4, -3, -2, -3, -4, 2, -2, -1, -2, -2, 3, -4, -2, -2, 2, 7},
+ { 0, -3, -3, -4, -1, -2, -3, -4, -4, 3, 1, -3, 1, -1, -3, -2, 0, -3, -2, 4}};
+
+ public Blosum75() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum80.java b/src/jebl/evolution/align/scores/Blosum80.java
new file mode 100644
index 0000000..b58f2ff
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum80.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum80 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 7},
+ { -3, 9},
+ { -3, -1, 9},
+ { -3, -3, 2, 10},
+ { -1, -6, -5, -7, 13},
+ { -2, 1, 0, -1, -5, 9},
+ { -2, -1, -1, 2, -7, 3, 8},
+ { 0, -4, -1, -3, -6, -4, -4, 9},
+ { -3, 0, 1, -2, -7, 1, 0, -4, 12},
+ { -3, -5, -6, -7, -2, -5, -6, -7, -6, 7},
+ { -3, -4, -6, -7, -3, -4, -6, -7, -5, 2, 6},
+ { -1, 3, 0, -2, -6, 2, 1, -3, -1, -5, -4, 8},
+ { -2, -3, -4, -6, -3, -1, -4, -5, -4, 2, 3, -3, 9},
+ { -4, -5, -6, -6, -4, -5, -6, -6, -2, -1, 0, -5, 0, 10},
+ { -1, -3, -4, -3, -6, -3, -2, -5, -4, -5, -5, -2, -4, -6, 12},
+ { 2, -2, 1, -1, -2, -1, -1, -1, -2, -4, -4, -1, -3, -4, -2, 7},
+ { 0, -2, 0, -2, -2, -1, -2, -3, -3, -2, -3, -1, -1, -4, -3, 2, 8},
+ { -5, -5, -7, -8, -5, -4, -6, -6, -4, -5, -4, -6, -3, 0, -7, -6, -5, 16},
+ { -4, -4, -4, -6, -5, -3, -5, -6, 3, -3, -2, -4, -3, 4, -6, -3, -3, 3, 11},
+ { -1, -4, -5, -6, -2, -4, -4, -6, -5, 4, 1, -4, 1, -2, -4, -3, 0, -5, -3, 7}};
+
+ public Blosum80() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum85.java b/src/jebl/evolution/align/scores/Blosum85.java
new file mode 100644
index 0000000..cc597f1
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum85.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum85 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 5},
+ { -2, 6},
+ { -2, -1, 7},
+ { -2, -2, 1, 7},
+ { -1, -4, -4, -5, 9},
+ { -1, 1, 0, -1, -4, 6},
+ { -1, -1, -1, 1, -5, 2, 6},
+ { 0, -3, -1, -2, -4, -3, -3, 6},
+ { -2, 0, 0, -2, -5, 1, -1, -3, 8},
+ { -2, -4, -4, -5, -2, -4, -4, -5, -4, 5},
+ { -2, -3, -4, -5, -2, -3, -4, -5, -3, 1, 4},
+ { -1, 2, 0, -1, -4, 1, 0, -2, -1, -3, -3, 6},
+ { -2, -2, -3, -4, -2, 0, -3, -4, -3, 1, 2, -2, 7},
+ { -3, -4, -4, -4, -3, -4, -4, -4, -2, -1, 0, -4, -1, 7},
+ { -1, -2, -3, -2, -4, -2, -2, -3, -3, -4, -4, -2, -3, -4, 8},
+ { 1, -1, 0, -1, -2, -1, -1, -1, -1, -3, -3, -1, -2, -3, -1, 5},
+ { 0, -2, 0, -2, -2, -1, -1, -2, -2, -1, -2, -1, -1, -3, -2, 1, 5},
+ { -3, -4, -5, -6, -4, -3, -4, -4, -3, -3, -3, -5, -2, 0, -5, -4, -4, 11},
+ { -3, -3, -3, -4, -3, -2, -4, -5, 2, -2, -2, -3, -2, 3, -4, -2, -2, 2, 7},
+ { -1, -3, -4, -4, -1, -3, -3, -4, -4, 3, 0, -3, 0, -1, -3, -2, 0, -3, -2, 5}};
+
+ public Blosum85() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Blosum90.java b/src/jebl/evolution/align/scores/Blosum90.java
new file mode 100644
index 0000000..166555c
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Blosum90.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Blosum90 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 5},
+ { -2, 6},
+ { -2, -1, 7},
+ { -3, -3, 1, 7},
+ { -1, -5, -4, -5, 9},
+ { -1, 1, 0, -1, -4, 7},
+ { -1, -1, -1, 1, -6, 2, 6},
+ { 0, -3, -1, -2, -4, -3, -3, 6},
+ { -2, 0, 0, -2, -5, 1, -1, -3, 8},
+ { -2, -4, -4, -5, -2, -4, -4, -5, -4, 5},
+ { -2, -3, -4, -5, -2, -3, -4, -5, -4, 1, 5},
+ { -1, 2, 0, -1, -4, 1, 0, -2, -1, -4, -3, 6},
+ { -2, -2, -3, -4, -2, 0, -3, -4, -3, 1, 2, -2, 7},
+ { -3, -4, -4, -5, -3, -4, -5, -5, -2, -1, 0, -4, -1, 7},
+ { -1, -3, -3, -3, -4, -2, -2, -3, -3, -4, -4, -2, -3, -4, 8},
+ { 1, -1, 0, -1, -2, -1, -1, -1, -2, -3, -3, -1, -2, -3, -2, 5},
+ { 0, -2, 0, -2, -2, -1, -1, -3, -2, -1, -2, -1, -1, -3, -2, 1, 6},
+ { -4, -4, -5, -6, -4, -3, -5, -4, -3, -4, -3, -5, -2, 0, -5, -4, -4, 11},
+ { -3, -3, -3, -4, -4, -3, -4, -5, 1, -2, -2, -3, -2, 3, -4, -3, -2, 2, 8},
+ { -1, -3, -4, -5, -2, -3, -3, -5, -4, 3, 0, -3, 0, -2, -3, -2, -1, -3, -3, 5}};
+
+ public Blosum90() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Hamming.java b/src/jebl/evolution/align/scores/Hamming.java
new file mode 100644
index 0000000..8bba0c9
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Hamming.java
@@ -0,0 +1,26 @@
+package jebl.evolution.align.scores;
+
+
+/**
+ * @author Andrew Rambaut
+ *
+ * @version $Id: Hamming.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+
+public class Hamming extends NucleotideScores {
+
+ private final float[][] residueScores = {
+
+ /* A C G T U */
+ { 0},
+ { -1, 0},
+ { -1, -1, 0},
+ { -1, -1, -1, 0},
+ { -1, -1, -1, 0, 0}};
+
+ public Hamming() { buildScores(residueScores); }
+
+ public String toString() {
+ return "Hamming (0/-1)";
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/scores/JukesCantor.java b/src/jebl/evolution/align/scores/JukesCantor.java
new file mode 100644
index 0000000..fbf3ff5
--- /dev/null
+++ b/src/jebl/evolution/align/scores/JukesCantor.java
@@ -0,0 +1,29 @@
+package jebl.evolution.align.scores;
+
+/**
+ * Jukes Cantor assumes equal substitution frequencies and equal nucleotide
+ * equilibrium frequencies.
+ *
+ * @author Richard Moir
+ *
+ * @version $Id: JukesCantor.java 319 2006-05-04 00:16:20Z matt_kearse $
+ */
+public class JukesCantor extends NucleotideScores {
+
+ /**
+ *
+ * @param d evolutionary distance used to calculate values
+ */
+ public JukesCantor(float d) {
+ name = "JukesCantor";
+
+ double p = (0.25 + 0.75 * Math.exp(-4.0/3.0 * d)); //diagonal values on the substitution matrix.
+ match = (float)(Math.log(p/0.25)/Math.log(2.0)); //convert to log-odds (binomial logarithm).
+
+ double q = (1.0 - p)/3.0; //off-diagonal values.
+ float mismatch = (float)(Math.log(q/0.25)/Math.log(2.0));
+
+ buildScores(match, 0,mismatch, mismatch, false);
+ }
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/scores/NucleotideScores.java b/src/jebl/evolution/align/scores/NucleotideScores.java
new file mode 100644
index 0000000..8c5ab03
--- /dev/null
+++ b/src/jebl/evolution/align/scores/NucleotideScores.java
@@ -0,0 +1,159 @@
+package jebl.evolution.align.scores;
+
+import jebl.evolution.sequences.NucleotideState;
+import jebl.evolution.sequences.Nucleotides;
+import jebl.evolution.sequences.State;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Richard Moir
+ * @author Alexei Drummond
+ *
+ * @version $Id: NucleotideScores.java 669 2007-03-27 23:19:15Z matt_kearse $
+ *
+ */
+public class NucleotideScores extends Scores {
+
+ float match = 5;
+ float mismatchTransition = -4;
+ float mismatchTransversion = -4;
+ String name = "";
+ private boolean includeAmbiguities;
+ private String alphabet =
+ Nucleotides.CANONICAL_STATES[0].getCode() +
+ Nucleotides.CANONICAL_STATES[1].getCode() +
+ Nucleotides.CANONICAL_STATES[2].getCode() +
+ Nucleotides.CANONICAL_STATES[3].getCode() +"U";
+
+ public static final NucleotideScores IUB = new NucleotideScores(1.0f, -0.9f);
+ public static final NucleotideScores CLUSTALW = new NucleotideScores(1.0f, 0.0f);
+
+ protected NucleotideScores() {
+ }
+
+ public NucleotideScores(NucleotideScores scores) {
+ name = scores.name;
+ alphabet = scores.getAlphabet();
+ match = scores.match;
+ mismatchTransition = scores.mismatchTransition;
+ mismatchTransversion = scores.mismatchTransversion;
+ }
+
+ /**
+ * @param match match score
+ * @param misMatch mismatch score
+ */
+ public NucleotideScores(float match, float misMatch) {
+ this("", match, misMatch, misMatch);
+ }
+
+ public NucleotideScores(float match, float misMatch, float ambiguousMatch) {
+ this("", match, misMatch, misMatch, ambiguousMatch);
+ }
+
+ public NucleotideScores(String name, float match, float misMatch) {
+ this(name, match, misMatch, misMatch, 0);
+ }
+
+ public NucleotideScores(String name, float match, float mismatchTransition, float mismatchTransversion) {
+ this.name = name;
+ buildScores(match, mismatchTransition, mismatchTransversion, 0, false);
+ }
+
+ public NucleotideScores(String name, float match, float mismatchTransition, float mismatchTransversion, float ambiguousMatch) {
+ this.name = name;
+ buildScores(match, mismatchTransition, mismatchTransversion, ambiguousMatch, true);
+ }
+
+ public NucleotideScores(Scores scores, double percentmatches) {
+ double match = Math.log(percentmatches/(4 *.25 *.25));
+ double mismatch = Math.log((1-percentmatches)/(12 * .25 *.25));
+
+ // normalize match from scores
+ float ma = scores.score['A']['A'];
+ float mm = (float)(mismatch * (ma/match));
+
+ name = ((int)Math.round(100*percentmatches)) + "% similarity";
+ buildScores(ma, mm, mm, 0, true);
+ includeAdditionalCharacters(this, scores.getExtraResidues());
+ }
+
+ void buildScores(float match, float mismatchTransition, float mismatchTransversion, float ambiguousMatch, boolean includeAmbiguities) {
+
+ this.match = match;
+ this.mismatchTransition = mismatchTransition;
+ this.mismatchTransversion = mismatchTransversion;
+ this.includeAmbiguities = includeAmbiguities;
+
+// final int states = includeAmbiguities? Nucleotides.getStateCount():Nucleotides.getCanonicalStateCount();
+ List<NucleotideState> states = new ArrayList<NucleotideState>();
+ StringBuilder builder = new StringBuilder();
+ for (NucleotideState state : Nucleotides.STATES) {
+ if (state.isGap()) continue;
+ if (state.isAmbiguous()&& !includeAmbiguities) continue;
+ states.add (state);
+ builder.append (state.getCode ());
+ }
+ // Add RNA "U" and the corresponding canonical state which is T_STATE to the list:
+ alphabet = builder.toString() + "U";
+ states.add(Nucleotides.T_STATE);
+
+ int statesCount = states.size();
+ float[][] scores = new float[statesCount][statesCount];
+ for (int i = 0; i < statesCount; i++) {
+ State state1 = states.get(i);
+ for (int j = 0; j < statesCount; j++) {
+ State state2 = states.get(j);
+ float value;
+ if (state1.equals(state2)) {
+ value = match;
+ }
+ else if (state1.possiblyEqual(state2)) {
+ value = ambiguousMatch;
+ }
+ else if (
+ (Nucleotides.isPurine(state1) && Nucleotides.isPurine(state2)) ||
+ (Nucleotides.isPyrimidine(state1) && Nucleotides.isPyrimidine(state2)) ) {
+ value = mismatchTransition;
+ } else {
+ value = mismatchTransversion;
+ }
+
+ /* float val = (i == j) ? match :
+ ((isPurine(i) == isPurine(j)) ? mismatchTransition : mismatchTransversion);
+ */
+ scores[i][j] = value;
+ }
+ }
+ buildScores(scores);
+ }
+
+ /*
+ private boolean isPurine(int state) {
+ return Nucleotides.isPurine(Nucleotides.CANONICAL_STATES[state]);
+ }
+ */
+
+ public String getName() {
+ return name;
+ }
+
+ public final String getAlphabet() {
+ return alphabet + getExtraResidues ();
+ }
+
+ public String toString() {
+ String result = match + "/" + mismatchTransition;
+ if(mismatchTransversion != mismatchTransition) {
+ result = result + "/" + mismatchTransversion;
+ }
+ if(name.length()> 0){
+ result = name + " (" + result + ")";
+ }
+ return result;
+ }
+}
+
diff --git a/src/jebl/evolution/align/scores/Pam100.java b/src/jebl/evolution/align/scores/Pam100.java
new file mode 100644
index 0000000..b457ee0
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam100.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam100 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 4},
+ { -3, 7},
+ { -1, -2, 5},
+ { -1, -4, 3, 5},
+ { -3, -5, -5, -7, 9},
+ { -2, 1, -1, 0, -8, 6},
+ { 0, -3, 1, 4, -8, 2, 5},
+ { 1, -5, -1, -1, -5, -3, -1, 5},
+ { -3, 1, 2, -1, -4, 3, -1, -4, 7},
+ { -2, -3, -3, -4, -3, -4, -3, -5, -4, 6},
+ { -3, -5, -4, -6, -8, -2, -5, -6, -3, 1, 6},
+ { -3, 2, 1, -1, -8, 0, -1, -3, -2, -3, -4, 5},
+ { -2, -1, -4, -5, -7, -2, -4, -4, -4, 1, 3, 0, 9},
+ { -5, -6, -5, -8, -7, -7, -8, -6, -3, 0, 0, -7, -1, 8},
+ { 1, -1, -2, -3, -4, -1, -2, -2, -1, -4, -4, -3, -4, -6, 7},
+ { 1, -1, 1, -1, -1, -2, -1, 0, -2, -3, -4, -1, -3, -4, 0, 4},
+ { 1, -3, 0, -2, -4, -2, -2, -2, -3, 0, -3, -1, -1, -5, -1, 2, 5},
+ { -7, 1, -5, -9, -9, -7, -9, -9, -4, -7, -3, -6, -6, -1, -7, -3, -7, 12},
+ { -4, -6, -2, -6, -1, -6, -5, -7, -1, -3, -3, -6, -5, 4, -7, -4, -4, -2, 9},
+ { 0, -4, -3, -4, -3, -3, -3, -3, -3, 3, 0, -4, 1, -3, -3, -2, 0, -9, -4, 5}};
+
+ public Pam100() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam110.java b/src/jebl/evolution/align/scores/Pam110.java
new file mode 100644
index 0000000..da9b117
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam110.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam110 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -3, 7},
+ { -1, -1, 4},
+ { -1, -4, 2, 5},
+ { -3, -4, -5, -7, 9},
+ { -1, 1, 0, 1, -7, 6},
+ { 0, -3, 1, 4, -7, 2, 5},
+ { 1, -4, 0, 0, -5, -3, -1, 5},
+ { -3, 1, 2, 0, -4, 3, -1, -4, 7},
+ { -1, -3, -2, -3, -3, -3, -3, -4, -4, 6},
+ { -3, -4, -4, -6, -8, -2, -5, -6, -3, 1, 6},
+ { -3, 2, 1, -1, -7, 0, -1, -3, -2, -3, -4, 5},
+ { -2, -1, -3, -5, -7, -1, -3, -4, -4, 1, 3, 0, 8},
+ { -4, -5, -4, -7, -6, -6, -7, -5, -3, 0, 0, -7, -1, 8},
+ { 1, -1, -2, -3, -4, 0, -2, -2, -1, -4, -4, -3, -4, -6, 6},
+ { 1, -1, 1, -1, -1, -2, -1, 0, -2, -3, -4, -1, -2, -4, 0, 3},
+ { 1, -2, 0, -1, -3, -2, -2, -2, -3, 0, -3, -1, -1, -4, -1, 2, 5},
+ { -7, 1, -5, -8, -9, -6, -9, -8, -4, -7, -3, -5, -6, -1, -7, -3, -6, 12},
+ { -4, -5, -2, -5, -1, -6, -5, -7, -1, -2, -3, -5, -5, 4, -7, -3, -3, -2, 8},
+ { 0, -4, -3, -4, -3, -3, -3, -2, -3, 3, 1, -4, 1, -3, -2, -2, 0, -8, -4, 5}};
+
+ public Pam110() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam120.java b/src/jebl/evolution/align/scores/Pam120.java
new file mode 100644
index 0000000..4a7794f
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam120.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam120 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -3, 6},
+ { -1, -1, 4},
+ { 0, -3, 2, 5},
+ { -3, -4, -5, -7, 9},
+ { -1, 1, 0, 1, -7, 6},
+ { 0, -3, 1, 3, -7, 2, 5},
+ { 1, -4, 0, 0, -4, -3, -1, 5},
+ { -3, 1, 2, 0, -4, 3, -1, -4, 7},
+ { -1, -2, -2, -3, -3, -3, -3, -4, -4, 6},
+ { -3, -4, -4, -5, -7, -2, -4, -5, -3, 1, 5},
+ { -2, 2, 1, -1, -7, 0, -1, -3, -2, -3, -4, 5},
+ { -2, -1, -3, -4, -6, -1, -3, -4, -4, 1, 3, 0, 8},
+ { -4, -5, -4, -7, -6, -6, -7, -5, -3, 0, 0, -7, -1, 8},
+ { 1, -1, -2, -3, -4, 0, -2, -2, -1, -3, -3, -2, -3, -5, 6},
+ { 1, -1, 1, 0, 0, -2, -1, 1, -2, -2, -4, -1, -2, -3, 1, 3},
+ { 1, -2, 0, -1, -3, -2, -2, -1, -3, 0, -3, -1, -1, -4, -1, 2, 4},
+ { -7, 1, -4, -8, -8, -6, -8, -8, -3, -6, -3, -5, -6, -1, -7, -2, -6, 12},
+ { -4, -5, -2, -5, -1, -5, -5, -6, -1, -2, -2, -5, -4, 4, -6, -3, -3, -2, 8},
+ { 0, -3, -3, -3, -3, -3, -3, -2, -3, 3, 1, -4, 1, -3, -2, -2, 0, -8, -3, 5}};
+
+ public Pam120() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam130.java b/src/jebl/evolution/align/scores/Pam130.java
new file mode 100644
index 0000000..acbf704
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam130.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam130 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -3, 6},
+ { 0, -1, 4},
+ { 0, -3, 2, 5},
+ { -3, -4, -4, -6, 9},
+ { -1, 1, 0, 1, -6, 5},
+ { 0, -3, 1, 3, -6, 2, 5},
+ { 1, -4, 0, 0, -4, -2, -1, 5},
+ { -2, 1, 2, 0, -4, 3, 0, -3, 7},
+ { -1, -2, -2, -3, -3, -3, -2, -4, -3, 5},
+ { -3, -4, -3, -5, -7, -2, -4, -5, -3, 1, 5},
+ { -2, 2, 1, -1, -6, 0, -1, -3, -1, -2, -4, 5},
+ { -2, -1, -3, -4, -6, -1, -3, -4, -3, 2, 3, 0, 8},
+ { -4, -5, -4, -7, -5, -6, -6, -5, -2, 0, 1, -6, -1, 7},
+ { 1, -1, -1, -2, -3, 0, -2, -2, -1, -3, -3, -2, -3, -5, 6},
+ { 1, -1, 1, 0, 0, -1, -1, 1, -2, -2, -4, -1, -2, -3, 1, 3},
+ { 1, -2, 0, -1, -3, -2, -1, -1, -2, 0, -2, 0, -1, -4, -1, 2, 4},
+ { -6, 1, -4, -7, -8, -6, -8, -7, -3, -6, -2, -5, -5, -1, -6, -2, -6, 12},
+ { -4, -5, -2, -5, -1, -5, -5, -6, 0, -2, -2, -5, -4, 4, -6, -3, -3, -1, 8},
+ { 0, -3, -3, -3, -2, -3, -3, -2, -3, 3, 1, -4, 1, -2, -2, -2, 0, -7, -3, 5}};
+
+ public Pam130() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam140.java b/src/jebl/evolution/align/scores/Pam140.java
new file mode 100644
index 0000000..403b75c
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam140.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam140 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -2, 6},
+ { 0, -1, 3},
+ { 0, -3, 2, 4},
+ { -2, -4, -4, -6, 9},
+ { -1, 1, 0, 1, -6, 5},
+ { 0, -2, 1, 3, -6, 2, 4},
+ { 1, -4, 0, 0, -4, -2, -1, 5},
+ { -2, 1, 2, 0, -4, 3, 0, -3, 6},
+ { -1, -2, -2, -3, -3, -3, -2, -4, -3, 5},
+ { -2, -4, -3, -5, -7, -2, -4, -5, -2, 1, 5},
+ { -2, 3, 1, -1, -6, 0, -1, -3, -1, -2, -3, 5},
+ { -2, -1, -2, -4, -6, -1, -3, -3, -3, 2, 3, 0, 7},
+ { -4, -5, -4, -6, -5, -5, -6, -5, -2, 0, 1, -6, -1, 7},
+ { 1, -1, -1, -2, -3, 0, -1, -1, -1, -3, -3, -2, -3, -5, 6},
+ { 1, -1, 1, 0, 0, -1, -1, 1, -1, -2, -3, -1, -2, -3, 1, 3},
+ { 1, -2, 0, -1, -3, -2, -1, -1, -2, 0, -2, 0, -1, -4, 0, 2, 4},
+ { -6, 1, -4, -7, -8, -5, -8, -7, -3, -6, -2, -4, -5, -1, -6, -2, -5, 12},
+ { -4, -5, -2, -5, -1, -5, -4, -6, 0, -2, -2, -5, -4, 4, -6, -3, -3, -1, 8},
+ { 0, -3, -2, -3, -2, -2, -2, -2, -3, 3, 1, -3, 1, -2, -2, -2, 0, -7, -3, 5}};
+
+ public Pam140() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam150.java b/src/jebl/evolution/align/scores/Pam150.java
new file mode 100644
index 0000000..e1bebc0
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam150.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam150 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -2, 6},
+ { 0, -1, 3},
+ { 0, -2, 2, 4},
+ { -2, -4, -4, -6, 9},
+ { -1, 1, 0, 1, -6, 5},
+ { 0, -2, 1, 3, -6, 2, 4},
+ { 1, -3, 0, 0, -4, -2, -1, 4},
+ { -2, 1, 2, 0, -3, 3, 0, -3, 6},
+ { -1, -2, -2, -3, -2, -3, -2, -3, -3, 5},
+ { -2, -3, -3, -5, -6, -2, -4, -4, -2, 1, 5},
+ { -2, 3, 1, -1, -6, 0, -1, -2, -1, -2, -3, 4},
+ { -1, -1, -2, -3, -5, -1, -2, -3, -3, 2, 3, 0, 7},
+ { -4, -4, -4, -6, -5, -5, -6, -5, -2, 0, 1, -6, -1, 7},
+ { 1, -1, -1, -2, -3, 0, -1, -1, -1, -3, -3, -2, -3, -5, 6},
+ { 1, -1, 1, 0, 0, -1, -1, 1, -1, -2, -3, -1, -2, -3, 1, 2},
+ { 1, -2, 0, -1, -3, -1, -1, -1, -2, 0, -2, 0, -1, -3, 0, 1, 4},
+ { -6, 1, -4, -7, -7, -5, -7, -7, -3, -5, -2, -4, -5, -1, -6, -2, -5, 12},
+ { -3, -4, -2, -4, 0, -4, -4, -5, 0, -2, -2, -4, -3, 5, -5, -3, -3, -1, 8},
+ { 0, -3, -2, -3, -2, -2, -2, -2, -3, 3, 1, -3, 1, -2, -2, -1, 0, -6, -3, 4}};
+
+ public Pam150() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam160.java b/src/jebl/evolution/align/scores/Pam160.java
new file mode 100644
index 0000000..0baac77
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam160.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam160 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 6},
+ { 0, -1, 3},
+ { 0, -2, 2, 4},
+ { -2, -3, -4, -5, 9},
+ { -1, 1, 0, 1, -5, 5},
+ { 0, -2, 1, 3, -5, 2, 4},
+ { 1, -3, 0, 0, -3, -2, 0, 4},
+ { -2, 1, 2, 0, -3, 2, 0, -3, 6},
+ { -1, -2, -2, -3, -2, -2, -2, -3, -3, 5},
+ { -2, -3, -3, -4, -6, -2, -3, -4, -2, 2, 5},
+ { -2, 3, 1, 0, -5, 0, -1, -2, -1, -2, -3, 4},
+ { -1, -1, -2, -3, -5, -1, -2, -3, -3, 2, 3, 0, 7},
+ { -3, -4, -3, -6, -5, -5, -5, -4, -2, 0, 1, -5, 0, 7},
+ { 1, -1, -1, -2, -3, 0, -1, -1, -1, -2, -3, -2, -2, -4, 5},
+ { 1, -1, 1, 0, 0, -1, 0, 1, -1, -2, -3, -1, -2, -3, 1, 2},
+ { 1, -1, 0, -1, -2, -1, -1, -1, -2, 0, -2, 0, -1, -3, 0, 1, 3},
+ { -5, 1, -4, -6, -7, -5, -7, -7, -3, -5, -2, -4, -4, -1, -5, -2, -5, 12},
+ { -3, -4, -2, -4, 0, -4, -4, -5, 0, -2, -2, -4, -3, 5, -5, -3, -3, -1, 8},
+ { 0, -3, -2, -3, -2, -2, -2, -2, -2, 3, 1, -3, 1, -2, -2, -1, 0, -6, -3, 4}};
+
+ public Pam160() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam170.java b/src/jebl/evolution/align/scores/Pam170.java
new file mode 100644
index 0000000..a3729fc
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam170.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam170 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -3, 8},
+ { 0, -1, 4},
+ { 0, -3, 3, 6},
+ { -3, -5, -5, -7, 13},
+ { -1, 1, 0, 2, -8, 6},
+ { 0, -2, 2, 5, -8, 3, 6},
+ { 1, -4, 0, 0, -5, -2, 0, 6},
+ { -3, 2, 2, 0, -5, 4, 0, -4, 9},
+ { -1, -3, -3, -4, -3, -3, -3, -4, -4, 7},
+ { -3, -4, -4, -6, -9, -2, -5, -6, -3, 2, 7},
+ { -2, 4, 1, -1, -8, 0, -1, -3, -1, -3, -4, 6},
+ { -2, -1, -3, -4, -7, -1, -3, -4, -4, 2, 4, 1, 10},
+ { -5, -6, -5, -8, -6, -7, -8, -6, -3, 1, 1, -8, 0, 10},
+ { 1, -1, -1, -2, -4, 0, -1, -2, -1, -3, -4, -2, -3, -6, 8},
+ { 2, -1, 1, 0, 0, -1, -1, 1, -2, -2, -4, -1, -2, -4, 1, 3},
+ { 2, -2, 0, -1, -3, -2, -1, -1, -2, 0, -3, 0, -1, -5, 0, 2, 5},
+ { -8, 2, -5, -9,-10, -7,-10, -9, -4, -7, -3, -5, -6, -1, -8, -3, -7, 18},
+ { -5, -6, -3, -6, 0, -6, -6, -7, 0, -2, -2, -6, -4, 7, -7, -4, -4, -1, 12},
+ { 0, -4, -3, -4, -3, -3, -3, -2, -3, 5, 2, -4, 2, -2, -2, -2, 0, -9, -4, 6}};
+
+ public Pam170() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam180.java b/src/jebl/evolution/align/scores/Pam180.java
new file mode 100644
index 0000000..2412f71
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam180.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam180 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -3, 8},
+ { 0, -1, 4},
+ { 0, -3, 3, 5},
+ { -3, -5, -5, -7, 13},
+ { -1, 1, 0, 2, -7, 6},
+ { 0, -2, 2, 4, -7, 3, 5},
+ { 1, -4, 0, 0, -5, -2, 0, 6},
+ { -2, 2, 2, 0, -4, 4, 0, -3, 8},
+ { -1, -3, -3, -3, -3, -3, -3, -4, -4, 6},
+ { -3, -4, -4, -6, -8, -2, -5, -6, -3, 2, 7},
+ { -2, 4, 1, 0, -7, 0, -1, -3, -1, -3, -4, 6},
+ { -2, -1, -3, -4, -7, -1, -3, -4, -3, 2, 4, 1, 9},
+ { -5, -6, -5, -8, -6, -6, -7, -6, -3, 1, 1, -7, 0, 10},
+ { 1, -1, -1, -2, -4, 0, -1, -1, -1, -3, -4, -2, -3, -6, 8},
+ { 1, -1, 1, 0, 0, -1, -1, 1, -2, -2, -4, -1, -2, -4, 1, 3},
+ { 2, -2, 0, -1, -3, -2, -1, -1, -2, 0, -3, 0, -1, -4, 0, 2, 4},
+ { -8, 2, -5, -9,-10, -6, -9, -9, -4, -7, -3, -5, -6, 0, -7, -3, -7, 18},
+ { -5, -6, -2, -6, 0, -6, -6, -7, 0, -2, -2, -6, -4, 7, -7, -4, -4, -1, 11},
+ { 0, -4, -3, -3, -3, -3, -3, -2, -3, 5, 2, -4, 2, -2, -2, -2, 0, -8, -4, 6}};
+
+ public Pam180() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam190.java b/src/jebl/evolution/align/scores/Pam190.java
new file mode 100644
index 0000000..de69615
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam190.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam190 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -2, 8},
+ { 0, -1, 3},
+ { 0, -2, 3, 5},
+ { -3, -5, -5, -7, 13},
+ { -1, 1, 1, 2, -7, 6},
+ { 0, -2, 2, 4, -7, 3, 5},
+ { 1, -4, 0, 0, -4, -2, 0, 6},
+ { -2, 2, 2, 0, -4, 3, 0, -3, 8},
+ { -1, -3, -2, -3, -3, -3, -3, -4, -3, 6},
+ { -3, -4, -4, -5, -8, -2, -4, -5, -3, 2, 7},
+ { -2, 4, 1, 0, -7, 1, -1, -3, -1, -3, -4, 6},
+ { -2, -1, -3, -4, -7, -1, -3, -4, -3, 2, 4, 1, 9},
+ { -5, -6, -4, -7, -6, -6, -7, -6, -2, 1, 2, -7, 0, 10},
+ { 1, 0, -1, -2, -4, 0, -1, -1, -1, -3, -3, -2, -3, -6, 7},
+ { 1, -1, 1, 0, 0, -1, 0, 1, -1, -2, -4, 0, -2, -4, 1, 3},
+ { 2, -2, 0, -1, -3, -1, -1, -1, -2, 0, -2, 0, -1, -4, 0, 2, 4},
+ { -7, 2, -5, -8, -9, -6, -9, -9, -3, -7, -3, -5, -6, 0, -7, -3, -6, 18},
+ { -4, -5, -2, -5, 0, -5, -5, -7, 0, -2, -2, -6, -4, 7, -6, -4, -3, -1, 11},
+ { 0, -3, -3, -3, -3, -3, -3, -2, -3, 4, 2, -3, 2, -2, -2, -2, 0, -8, -3, 6}};
+
+ public Pam190() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam200.java b/src/jebl/evolution/align/scores/Pam200.java
new file mode 100644
index 0000000..e315410
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam200.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam200 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 3},
+ { -2, 7},
+ { 0, 0, 3},
+ { 0, -2, 3, 5},
+ { -3, -4, -5, -6, 12},
+ { -1, 1, 1, 2, -7, 5},
+ { 0, -2, 2, 4, -7, 3, 5},
+ { 1, -4, 0, 0, -4, -2, 0, 6},
+ { -2, 2, 2, 0, -4, 3, 0, -3, 8},
+ { -1, -2, -2, -3, -3, -3, -3, -3, -3, 6},
+ { -2, -4, -4, -5, -7, -2, -4, -5, -3, 2, 7},
+ { -2, 4, 1, 0, -7, 1, 0, -2, -1, -2, -4, 6},
+ { -2, -1, -2, -4, -6, -1, -3, -4, -3, 2, 4, 1, 8},
+ { -4, -5, -4, -7, -6, -6, -7, -6, -2, 1, 2, -7, 0, 10},
+ { 1, 0, -1, -2, -4, 0, -1, -1, -1, -3, -3, -2, -3, -6, 7},
+ { 1, -1, 1, 0, 0, -1, 0, 1, -1, -2, -4, 0, -2, -4, 1, 2},
+ { 1, -1, 0, 0, -3, -1, -1, 0, -2, 0, -2, 0, -1, -4, 0, 2, 4},
+ { -7, 2, -5, -8, -9, -6, -9, -8, -3, -6, -2, -4, -5, 0, -7, -3, -6, 18},
+ { -4, -5, -2, -5, 0, -5, -5, -6, 0, -2, -2, -5, -3, 7, -6, -3, -3, -1, 11},
+ { 0, -3, -2, -3, -2, -3, -2, -2, -3, 4, 2, -3, 2, -2, -2, -1, 0, -8, -3, 5}};
+
+ public Pam200() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam210.java b/src/jebl/evolution/align/scores/Pam210.java
new file mode 100644
index 0000000..f65872b
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam210.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam210 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 7},
+ { 0, 0, 3},
+ { 0, -2, 2, 5},
+ { -2, -4, -4, -6, 12},
+ { -1, 1, 1, 2, -6, 5},
+ { 0, -2, 2, 4, -6, 3, 5},
+ { 1, -3, 0, 0, -4, -2, 0, 5},
+ { -2, 2, 2, 1, -4, 3, 0, -3, 8},
+ { -1, -2, -2, -3, -3, -3, -2, -3, -3, 5},
+ { -2, -4, -3, -5, -7, -2, -4, -5, -2, 2, 7},
+ { -2, 4, 1, 0, -6, 1, 0, -2, 0, -2, -3, 5},
+ { -1, -1, -2, -3, -6, -1, -3, -3, -3, 2, 4, 1, 8},
+ { -4, -5, -4, -7, -5, -6, -6, -5, -2, 1, 2, -6, 0, 10},
+ { 1, 0, -1, -1, -3, 0, -1, -1, 0, -3, -3, -2, -3, -5, 7},
+ { 1, 0, 1, 0, 0, -1, 0, 1, -1, -2, -3, 0, -2, -4, 1, 2},
+ { 1, -1, 0, 0, -3, -1, -1, 0, -2, 0, -2, 0, -1, -4, 0, 2, 3},
+ { -7, 2, -5, -8, -9, -6, -8, -8, -3, -6, -2, -4, -5, 0, -7, -3, -6, 18},
+ { -4, -5, -2, -5, 0, -5, -5, -6, 0, -1, -1, -5, -3, 7, -6, -3, -3, -1, 11},
+ { 0, -3, -2, -3, -2, -2, -2, -2, -3, 4, 2, -3, 2, -2, -2, -1, 0, -7, -3, 5}};
+
+ public Pam210() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam220.java b/src/jebl/evolution/align/scores/Pam220.java
new file mode 100644
index 0000000..cf47509
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam220.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam220 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 7},
+ { 0, 0, 3},
+ { 0, -2, 2, 4},
+ { -2, -4, -4, -6, 12},
+ { -1, 1, 1, 2, -6, 5},
+ { 0, -1, 2, 4, -6, 3, 4},
+ { 1, -3, 0, 0, -4, -2, 0, 5},
+ { -2, 2, 2, 1, -4, 3, 1, -3, 7},
+ { -1, -2, -2, -3, -3, -2, -2, -3, -3, 5},
+ { -2, -3, -3, -5, -7, -2, -4, -5, -2, 2, 6},
+ { -1, 4, 1, 0, -6, 1, 0, -2, 0, -2, -3, 5},
+ { -1, -1, -2, -3, -6, -1, -2, -3, -3, 2, 4, 1, 8},
+ { -4, -5, -4, -6, -5, -5, -6, -5, -2, 1, 2, -6, 0, 10},
+ { 1, 0, -1, -1, -3, 0, -1, -1, 0, -2, -3, -1, -2, -5, 7},
+ { 1, 0, 1, 0, 0, -1, 0, 1, -1, -2, -3, 0, -2, -4, 1, 2},
+ { 1, -1, 0, 0, -3, -1, -1, 0, -2, 0, -2, 0, -1, -4, 0, 2, 3},
+ { -6, 2, -4, -8, -8, -5, -8, -8, -3, -6, -2, -4, -5, 0, -6, -3, -6, 17},
+ { -4, -5, -2, -5, 0, -5, -5, -6, 0, -1, -1, -5, -3, 7, -6, -3, -3, 0, 11},
+ { 0, -3, -2, -3, -2, -2, -2, -2, -3, 4, 2, -3, 2, -2, -1, -1, 0, -7, -3, 5}};
+
+ public Pam220() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam230.java b/src/jebl/evolution/align/scores/Pam230.java
new file mode 100644
index 0000000..4d160a1
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam230.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam230 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 7},
+ { 0, 0, 2},
+ { 0, -2, 2, 4},
+ { -2, -4, -4, -6, 12},
+ { -1, 1, 1, 2, -6, 5},
+ { 0, -1, 1, 4, -6, 3, 4},
+ { 1, -3, 0, 1, -4, -1, 0, 5},
+ { -2, 2, 2, 1, -4, 3, 1, -2, 7},
+ { -1, -2, -2, -3, -2, -2, -2, -3, -3, 5},
+ { -2, -3, -3, -4, -7, -2, -4, -4, -2, 2, 6},
+ { -1, 4, 1, 0, -6, 1, 0, -2, 0, -2, -3, 5},
+ { -1, -1, -2, -3, -6, -1, -2, -3, -2, 2, 4, 0, 7},
+ { -4, -5, -4, -6, -5, -5, -6, -5, -2, 1, 2, -6, 0, 9},
+ { 1, 0, -1, -1, -3, 0, -1, -1, 0, -2, -3, -1, -2, -5, 6},
+ { 1, 0, 1, 0, 0, -1, 0, 1, -1, -2, -3, 0, -2, -3, 1, 2},
+ { 1, -1, 0, 0, -2, -1, -1, 0, -2, 0, -2, 0, -1, -3, 0, 2, 3},
+ { -6, 2, -4, -7, -8, -5, -8, -7, -3, -6, -2, -4, -5, 0, -6, -3, -6, 17},
+ { -4, -5, -2, -5, 0, -4, -5, -6, 0, -1, -1, -5, -3, 7, -5, -3, -3, 0, 10},
+ { 0, -3, -2, -2, -2, -2, -2, -2, -2, 4, 2, -3, 2, -1, -1, -1, 0, -7, -3, 5}};
+
+ public Pam230() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam240.java b/src/jebl/evolution/align/scores/Pam240.java
new file mode 100644
index 0000000..70d822b
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam240.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam240 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 6},
+ { 0, 0, 2},
+ { 0, -1, 2, 4},
+ { -2, -4, -4, -5, 12},
+ { 0, 1, 1, 2, -6, 4},
+ { 0, -1, 1, 4, -6, 3, 4},
+ { 1, -3, 0, 1, -4, -1, 0, 5},
+ { -1, 2, 2, 1, -4, 3, 1, -2, 7},
+ { -1, -2, -2, -2, -2, -2, -2, -3, -3, 5},
+ { -2, -3, -3, -4, -6, -2, -3, -4, -2, 2, 6},
+ { -1, 3, 1, 0, -6, 1, 0, -2, 0, -2, -3, 5},
+ { -1, 0, -2, -3, -5, -1, -2, -3, -2, 2, 4, 0, 7},
+ { -4, -5, -4, -6, -5, -5, -6, -5, -2, 1, 2, -5, 0, 9},
+ { 1, 0, -1, -1, -3, 0, -1, -1, 0, -2, -3, -1, -2, -5, 6},
+ { 1, 0, 1, 0, 0, -1, 0, 1, -1, -1, -3, 0, -2, -3, 1, 2},
+ { 1, -1, 0, 0, -2, -1, 0, 0, -1, 0, -2, 0, -1, -3, 0, 1, 3},
+ { -6, 2, -4, -7, -8, -5, -7, -7, -3, -5, -2, -4, -4, 0, -6, -3, -5, 17},
+ { -4, -4, -2, -4, 0, -4, -4, -5, 0, -1, -1, -5, -3, 7, -5, -3, -3, 0, 10},
+ { 0, -3, -2, -2, -2, -2, -2, -1, -2, 4, 2, -3, 2, -1, -1, -1, 0, -6, -3, 4}};
+
+ public Pam240() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/Pam250.java b/src/jebl/evolution/align/scores/Pam250.java
new file mode 100644
index 0000000..a40800d
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Pam250.java
@@ -0,0 +1,30 @@
+package jebl.evolution.align.scores;
+
+public class Pam250 extends AminoAcidScores {
+
+ private final float[][] residueScores = {
+
+ /* A R N D C Q E G H I L K M F P S T W Y V */
+ { 2},
+ { -2, 6},
+ { 0, 0, 2},
+ { 0, -1, 2, 4},
+ { -2, -4, -4, -5, 12},
+ { 0, 1, 1, 2, -5, 4},
+ { 0, -1, 1, 3, -5, 2, 4},
+ { 1, -3, 0, 1, -3, -1, 0, 5},
+ { -1, 2, 2, 1, -3, 3, 1, -2, 6},
+ { -1, -2, -2, -2, -2, -2, -2, -3, -2, 5},
+ { -2, -3, -3, -4, -6, -2, -3, -4, -2, 2, 6},
+ { -1, 3, 1, 0, -5, 1, 0, -2, 0, -2, -3, 5},
+ { -1, 0, -2, -3, -5, -1, -2, -3, -2, 2, 4, 0, 6},
+ { -3, -4, -3, -6, -4, -5, -5, -5, -2, 1, 2, -5, 0, 9},
+ { 1, 0, 0, -1, -3, 0, -1, 0, 0, -2, -3, -1, -2, -5, 6},
+ { 1, 0, 1, 0, 0, -1, 0, 1, -1, -1, -3, 0, -2, -3, 1, 2},
+ { 1, -1, 0, 0, -2, -1, 0, 0, -1, 0, -2, 0, -1, -3, 0, 1, 3},
+ { -6, 2, -4, -7, -8, -5, -7, -7, -3, -5, -2, -3, -4, 0, -6, -2, -5, 17},
+ { -3, -4, -2, -4, 0, -4, -4, -5, 0, -1, -1, -4, -2, 7, -5, -3, -3, 0, 10},
+ { 0, -2, -2, -2, -2, -2, -2, -1, -2, 4, 2, -2, 2, -1, -1, -1, 0, -6, -2, 4}};
+
+ public Pam250() { buildScores(residueScores); }
+}
diff --git a/src/jebl/evolution/align/scores/ScoreMatrix.java b/src/jebl/evolution/align/scores/ScoreMatrix.java
new file mode 100644
index 0000000..8c12daa
--- /dev/null
+++ b/src/jebl/evolution/align/scores/ScoreMatrix.java
@@ -0,0 +1,24 @@
+package jebl.evolution.align.scores;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: ScoreMatrix.java 360 2006-06-22 07:42:48Z pepster $
+ */
+public interface ScoreMatrix {
+ /**
+ *
+ * @return human readable name
+ */
+ public String getName();
+
+ /**
+ * @return the score for matching char x with char y
+ */
+ float getScore(char x, char y);
+
+ /**
+ * @return a string containing the valid characters for this score matrix.
+ */
+ String getAlphabet();
+}
diff --git a/src/jebl/evolution/align/scores/Scores.java b/src/jebl/evolution/align/scores/Scores.java
new file mode 100644
index 0000000..a7b6e2b
--- /dev/null
+++ b/src/jebl/evolution/align/scores/Scores.java
@@ -0,0 +1,207 @@
+package jebl.evolution.align.scores;
+
+/**
+ * Base class for all score matrices in the package.
+ *
+ * @author Alexei Drummond
+ *
+ * @version $Id: Scores.java 638 2007-02-06 20:31:30Z matt_kearse $
+ *
+ * Based on code originally by Peter Setsoft. See package.html.
+ */
+public abstract class Scores implements ScoreMatrix {
+
+ public float[][] score;
+ private String extraResidues = "";
+
+ /**
+ * @param scores float[][] with position [i][j] holding the score for
+ * getScore(getAlphabet().charAt(i), getAlphabet().charAt(j)).
+ */
+ protected void buildScores(float[][] scores) {
+ String states = getAlphabet().toUpperCase();
+ // Allow lowercase and uppercase states (ASCII code <= 127):
+ score = new float[127][127];
+ for (int i=0; i<states.length(); i++) {
+ char a = states.charAt(i);
+ char lca = Character.toLowerCase(a);
+ for (int j=0; j<=i; j++) {
+ char b = states.charAt(j);
+ char lcb = Character.toLowerCase(b);
+ score[a][b] = score[b][a]
+ = score[a][lcb] = score[lcb][a]
+ = score[lca][b] = score[b][lca]
+ = score[lca][lcb] = score[lcb][lca]
+ = scores[i][j];
+ }
+ }
+ }
+
+
+ void buildScores(float match, float mismatch) {
+ int states = getAlphabet().length();
+ float[][] scores = new float[states][states];
+
+ for (int i = 0; i < states; i++) {
+ for (int j = 0; j < states; j++) {
+ if (i == j) {
+ scores[i][j] = match;
+ } else {
+ scores[i][j] = mismatch;
+ }
+ }
+ }
+ buildScores(scores);
+ }
+
+ public final float getScore(char x, char y) {
+ return score[x][y];
+ }
+
+ public String toString() {
+ String name = getClass().getName();
+ return name.substring(name.lastIndexOf(".")+1);
+ }
+
+ /**
+ * @param scoreMatrix A ScoreMatrix with only low ascii characters (< chr(127)) in the alphabet
+ * @return A Scores instance corresponding to scoreMatrix.
+ */
+ public static Scores forMatrix(ScoreMatrix scoreMatrix) {
+ final String alphabet = scoreMatrix.getAlphabet();
+ final String name = scoreMatrix.getName();
+ float[][] scores = new float[alphabet.length()][alphabet.length()];
+ for (int i=0; i < alphabet.length(); i++) {
+ char a = alphabet.charAt(i);
+ for (int j=0; j < alphabet.length(); j++) {
+ char b = alphabet.charAt(j);
+ scores[i][j] = scoreMatrix.getScore(a,b);
+ }
+ }
+ Scores result = new Scores() {
+ public String getAlphabet() {
+ return alphabet;
+ }
+
+ public String getName() {
+ return name;
+ }
+ };
+ result.buildScores(scores);
+ return result;
+ }
+
+ public static Scores duplicate(Scores scores) {
+ Scores result;
+ if(scores instanceof AminoAcidScores) {
+ result = new AminoAcidScores();
+ } else if (scores instanceof NucleotideScores) {
+ result = new NucleotideScores((NucleotideScores) scores);
+ } else {
+ // what was part of the extra residues in the original class now becomes normal residues,
+ // just as in duplicate().
+ final String alphabet = scores.getAlphabet();
+ final String name = scores.getName();
+ result = new Scores() {
+ public String getAlphabet() {
+ return alphabet + getExtraResidues();
+ }
+ public String getName() {
+ return name;
+ }
+ };
+ }
+ result.extraResidues = scores.getExtraResidues();
+ result.score = new float[127][127];
+ for (int i = 0; i < 127; i++) {
+ System.arraycopy(scores.score[i], 0, result.score[i], 0, 127);
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @param scores
+ * @param gapVersusResidueCost should be a negative value
+ * @param gapVersusGapCost should be a positive value
+ */
+ public static Scores includeGaps(Scores scores, float gapVersusResidueCost, float gapVersusGapCost) {
+// System.out.println("cost =" + gapVersusResidueCost+ "," + gapVersusGapCost);
+ Scores result = duplicate(scores);
+ String states = scores.getAlphabet();
+ for (int i = 0; i < states.length(); i++) {
+ char res1 = states.charAt(i);
+ result.score['-'] [res1] = gapVersusResidueCost;
+ result.score[res1]['-'] = gapVersusResidueCost;
+ }
+ result.score['-']['-'] = gapVersusGapCost;
+ return result;
+ }
+
+ /**
+ * includes additional characters in the score matrix which will all have scored zero when compared to other
+ * characters.
+ *
+ * Current system does not handle special characters well, such as ? Or "R" for NucleotideSequences,
+ * which represents a "A" or "G".
+ * Currently, we just add all characters to the allowed set of characters, and they are scored as
+ * zero cost when comparing to other characters, including themselves. One-day, we should probably
+ * introduce better scoring system so that "R" is a positive score compared to "A" or "G",
+ * but a negative score compared to "C" or "T".
+ *
+ * example usage:
+ * scores = Scores.includeAdditionalCharacters(scores, "?ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ * @param scores
+ * @param characters
+ * @return a new score matrix.
+ */
+ public static Scores includeAdditionalCharacters(Scores scores, String characters) {
+ Scores result = duplicate(scores);
+ String states = scores.getAlphabet();
+ char[] unique = new char[characters.length ()];
+ int index = 0;
+ for (char character : characters.toCharArray()) {
+ if(states.indexOf(character)< 0) unique[index++ ]= character;
+ }
+ result.extraResidues =result.extraResidues+ new String(unique, 0, index);
+ // don't need to modify any of the "scores" values, since they all default to zero anyway.
+ return result;
+ }
+
+
+ protected String getExtraResidues() {
+ return extraResidues;
+ }
+
+
+ /**
+ * extends the given score matrix to include gap Versus gap and gap Versus residue costs
+ * The gap versus the gap cost is taken to be the same as the average residue match cost
+ * The gap in versus residue cost is taken to be the same as the average residue mismatch cost
+ * @param scores
+ */
+ // this function is a bad idea, don't use it.
+/* public static Scores includeGaps(Scores scores) {
+ float totalMismatch = 0;
+ float totalMatch = 0;
+ int mismatchCount= 0;
+ int matchCount = 0;
+ String states = scores.getAlphabet();
+ for (int i = 0; i < states.length(); i++) {
+ char res1 = states.charAt(i);
+ for (int j = 0; j < states.length(); j++) {
+ char res2 = states.charAt(j);
+ double score = scores.score[res1] [res2];
+ if(i==j) {
+ totalMatch += score;
+ matchCount ++;
+ }
+ else {
+ totalMismatch += score;
+ mismatchCount ++;
+ }
+ }
+ }
+ return includeGaps(scores, totalMismatch/mismatchCount-0.1f, totalMatch/matchCount);
+ }*/
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/scores/ScoresFactory.java b/src/jebl/evolution/align/scores/ScoresFactory.java
new file mode 100644
index 0000000..02d043b
--- /dev/null
+++ b/src/jebl/evolution/align/scores/ScoresFactory.java
@@ -0,0 +1,109 @@
+package jebl.evolution.align.scores;
+
+import java.lang.reflect.Constructor;
+
+public class ScoresFactory {
+
+ /**
+ * For any matrix.
+ *
+ * @param nameVal name and value of the matrix in String form. (eg Blosum45).
+ * @return substitution matrix of given name.
+ */
+ public static Scores generateScores(String nameVal) {
+ int i = 0;
+ while((int)nameVal.charAt(i) > 64) { //while(charAt(i) is a letter)
+ i++;
+ }
+ Scores sub = null;
+ String name = nameVal.substring(0, i);
+ String val = nameVal.substring(i, nameVal.length());
+ try {
+ if(val.indexOf(".") != -1)
+ sub = generateScores(name, Float.parseFloat(val));
+ else
+ sub = generateScores(name, Integer.parseInt(val));
+ }
+ catch(Exception e) {
+ System.out.println("no such substitution matrix!\n" + e);
+ }
+ return sub;
+ }
+
+ /**
+ * For Blosum and Pam matrices
+ *
+ * @param name "Blosum" or "Pam"
+ * @param val currently 45 - 90 or 100 - 250
+ * @return substitution matrix given by name and val.
+ */
+ public static Scores generateScores(String name, int val) {
+
+ Scores sub = null;
+ try {
+ Class c = Class.forName("jebl.evolution.align.scores." + name + val);
+ sub = (Scores)(c.newInstance());
+ }
+ catch(Exception e) {
+ System.out.println("no such substitution matrix!\n" + e);
+ }
+
+ return sub;
+
+ }
+
+ /**
+ * For calculated nucleotide matrices.
+ *
+ * @param name Currently only JukesCantor
+ * @param val val used to calculate matrix. eg. evolutionary distance d.
+ * @return substitution matrix calculated using val.
+ */
+ public static Scores generateScores(String name, float val) {
+
+ Scores sub = null;
+ try {
+ Class c = Class.forName("jebl.evolution.align.scores." + name);
+ Constructor con[] = c.getConstructors();
+ sub = (Scores)(con[0].newInstance(new Object[] {new Float(val)}));
+ }
+
+ catch(Exception e) {
+ System.out.println("no such substitution matrix!\n" + e);
+ }
+
+ return sub;
+ }
+
+ public static AminoAcidScores[] getAvailableAminoAcidScores () {
+ return new AminoAcidScores[] {new Blosum45(), new Blosum50(), new Blosum55 (), new Blosum60 (),
+ new Blosum62 (), new Blosum65 (), new Blosum70 (), new Blosum75 (), new Blosum80 (),
+ new Blosum85 (), new Blosum90 (), new Pam100(),new Pam110 (), new Pam120 (),
+ new Pam130(),new Pam140 (), new Pam150 (), new Pam160 (), new Pam170 (),
+ new Pam180(), new Pam190 (), new Pam200 (), new Pam210 (), new Pam220 (),
+ new Pam230 (), new Pam240 (), new Pam250 ()};
+ }
+ public static NucleotideScores[] getAvailableNucleotideScores () {
+ return new NucleotideScores[] {
+ new NucleotideScores("51% similarity", 5.0f, -3.0f),
+ new NucleotideScores("65% similarity", 5.0f, -4.0f),
+ // new NucleotideScores("70% similarity (IUB)", 1.0f, -0.9f),
+
+ // This seems like a bad choice as it implies a very high kappa
+ // new NucleotideScores("93% similarity, Transition/Transversion", 1, -1,-5, 0),
+
+ new NucleotideScores("70% similarity (IUB)", 5 * 1.0f, 5 * -0.9f),
+
+ // new NucleotideScores("93% similarity, Transition/Transversion", 5*1, 5*-1, 5*-5, 0),
+
+ // new NucleotideScores("88% similarity", 5.0f, -7.2810419984342278f),
+ new NucleotideScores("93% similarity", 5.0f, -9.0261674571825044f),
+
+
+// new NucleotideScores("Assembly", 10.0f, -9.0f),
+
+/* new NucleotideScores("Identity",1,0, 0, 0),*/
+
+ /*,new Hamming()*/};
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/align/scores/SubstScoreMatrix.java b/src/jebl/evolution/align/scores/SubstScoreMatrix.java
new file mode 100644
index 0000000..84a0450
--- /dev/null
+++ b/src/jebl/evolution/align/scores/SubstScoreMatrix.java
@@ -0,0 +1,48 @@
+package jebl.evolution.align.scores;
+
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.substmodel.RateMatrix;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: SubstScoreMatrix.java 360 2006-06-22 07:42:48Z pepster $
+ */
+public class SubstScoreMatrix extends Scores {
+
+ SequenceType sequenceType;
+ String alphabet;
+
+ public SubstScoreMatrix(RateMatrix rateMatrix) {
+
+ alphabet = SequenceType.Utils.getAlphabet(sequenceType);
+
+ int m = alphabet.length();
+
+ double[][] transProbs = new double[m][m];
+ rateMatrix.getTransitionProbabilities(transProbs);
+
+ buildScores(log(transProbs));
+ }
+
+ private float[][] log(double[][] values) {
+
+ float[][] logValues = new float[values.length][values[0].length];
+
+ for (int i = 0; i < values.length; i++) {
+ for (int j = 0; j < values[i].length; j++) {
+ logValues[i][j] = (float)Math.log(values[i][j]);
+ }
+ }
+
+ return logValues;
+ }
+
+ public String getName() {
+ return toString();
+ }
+
+ public String getAlphabet() {
+ return alphabet;
+ }
+}
diff --git a/src/jebl/evolution/aligners/Aligner.java b/src/jebl/evolution/aligners/Aligner.java
new file mode 100644
index 0000000..1008f4b
--- /dev/null
+++ b/src/jebl/evolution/aligners/Aligner.java
@@ -0,0 +1,26 @@
+package jebl.evolution.aligners;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+import jebl.util.ProgressListener;
+
+import java.util.Collection;
+
+/**
+ *
+ * As of 2006-12-06, this interface is not used anywhere in JEBL, and it doesn't have
+ * any implementing classes. It is only a proposed future alternative to the existing
+ * abstract class Align
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: Aligner.java 559 2006-12-06 22:20:38Z twobeers $
+ */
+public interface Aligner {
+
+ Alignment alignSequences(Collection<Sequence> sequences);
+
+ void addProgressListener(ProgressListener listener);
+
+ void removeProgressListener(ProgressListener listener);
+}
diff --git a/src/jebl/evolution/alignments/Alignment.java b/src/jebl/evolution/alignments/Alignment.java
new file mode 100644
index 0000000..f9c3508
--- /dev/null
+++ b/src/jebl/evolution/alignments/Alignment.java
@@ -0,0 +1,27 @@
+/*
+ * Alignment.java
+ *
+ * (c) 2005-2006 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.Sequences;
+
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Alignment.java 314 2006-05-03 01:21:14Z alexeidrummond $
+ */
+public interface Alignment extends Sequences, Patterns {
+
+ List<Sequence> getSequenceList();
+
+ int getSiteCount();
+}
diff --git a/src/jebl/evolution/alignments/BasicAlignment.java b/src/jebl/evolution/alignments/BasicAlignment.java
new file mode 100644
index 0000000..b3e53ef
--- /dev/null
+++ b/src/jebl/evolution/alignments/BasicAlignment.java
@@ -0,0 +1,241 @@
+/*
+ * BasicAlignment.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.*;
+
+/**
+ * A basic implementation of the Alignment interface.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: BasicAlignment.java 376 2006-07-06 05:37:16Z pepster $
+ */
+public class BasicAlignment implements Alignment {
+
+ /**
+ * Constructs a basic alignment with no sequences.
+ */
+ public BasicAlignment() {}
+
+ /**
+ * Constructs a basic alignment from a collection of sequences. The sequence
+ * objects are not copied.
+ * @param sequences
+ */
+ public BasicAlignment(Collection<? extends Sequence> sequences) {
+ for (Sequence sequence : sequences) {
+ put(sequence);
+ }
+ constructPatterns();
+ }
+
+ /**
+ * Constructs a basic alignment from an array of sequences. The sequence
+ * objects are not copied.
+ * @param sequences
+ */
+ public BasicAlignment(Sequence[] sequences) {
+ for (Sequence sequence : sequences) {
+ put(sequence);
+ }
+ constructPatterns();
+ }
+
+ /**
+ * @return a set containing all the sequences in this alignment.
+ */
+ public Set<Sequence> getSequences() {
+ return new HashSet<Sequence>(sequences.values());
+ }
+
+ public List<Sequence> getSequenceList() {
+ List<Sequence> seqs = new ArrayList<Sequence>();
+ for (Taxon taxon : taxonList) {
+ seqs.add(sequences.get(taxon));
+ }
+ return seqs;
+ }
+
+ public SequenceType getSequenceType() {
+ return sequenceType;
+ }
+
+ public Sequence getSequence(Taxon taxon) {
+ return sequences.get(taxon);
+ }
+
+ public int getSiteCount() {
+ return patterns.size();
+ }
+
+ public int getPatternCount() {
+ return patterns.size();
+ }
+
+ public int getPatternLength() {
+ return taxonList.size();
+ }
+
+ public List<Pattern> getPatterns() {
+ return patterns;
+ }
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ public List<Taxon> getTaxa() {
+ return taxonList;
+ }
+
+ /**
+ * Adds a sequence to this alignment
+ * @param sequence the new sequence.
+ */
+ public void addSequence(Sequence sequence) {
+ put(sequence);
+ constructPatterns();
+ }
+
+ private void put(Sequence sequence) {
+ if (sequenceType == null) {
+ sequenceType = sequence.getSequenceType();
+ }
+ if (sequenceType != sequence.getSequenceType()) {
+ throw new IllegalArgumentException(
+ "Type of sequence " + sequence.getTaxon().getName() +
+ " does not match that of other sequences in the alignment" +
+ " (data type = " + sequence.getSequenceType().getName() +
+ ", but expected " + sequenceType.getName() + ").");
+ }
+
+ if( taxonList.indexOf(sequence.getTaxon()) >= 0 ) {
+ throw new IllegalArgumentException("duplicate sequence name " + sequence.getTaxon());
+ }
+
+ sequences.put(sequence.getTaxon(), sequence);
+ taxonList.add(sequence.getTaxon());
+ }
+
+ private void constructPatterns() {
+ patterns.clear();
+
+ State[][] seqs = new State[sequences.size()][];
+ int i = 0;
+ int maxLen = 0;
+ for (Sequence seq : getSequenceList()) {
+ seqs[i] = seq.getStates();
+ if (seqs[i].length > maxLen) {
+ maxLen = seqs[i].length;
+ }
+ i++;
+ }
+
+ for (int j = 0; j < maxLen; j++) {
+ List<State> states = new ArrayList<State>();
+ for (i = 0; i < seqs.length; i++) {
+ if (j < seqs[i].length) {
+ states.add(seqs[i][j]);
+ } else {
+ states.add(sequenceType.getGapState());
+ }
+ }
+ patterns.add(new BasicPattern(states));
+ }
+ }
+
+ private SequenceType sequenceType = null;
+ private List<Taxon> taxonList = new ArrayList<Taxon>();
+ private Map<Taxon, Sequence> sequences = new HashMap<Taxon, Sequence>();
+ private List<Pattern> patterns = new ArrayList<Pattern>();
+
+ private class BasicPattern implements Pattern {
+
+ public BasicPattern(List<State> states) {
+ this.states = states;
+ }
+
+ /**
+ * @return the data type of the states in this pattern.
+ */
+ public SequenceType getSequenceType() {
+ return sequenceType;
+ }
+
+ public int getLength() {
+ return states.size();
+ }
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ public List<Taxon> getTaxa() {
+ return taxonList;
+ }
+
+ public State getState(int index) {
+ return states.get(index);
+ }
+
+ /**
+ * @return the list of state values of this pattern.
+ */
+ public List<State> getStates() {
+ return states;
+ }
+
+ /**
+ * @return the set of state values of this pattern.
+ */
+ public Set<State> getStateSet() {
+ return new HashSet<State>(states);
+ }
+
+ public double getWeight() {
+ return 1.0;
+ }
+
+ /**
+ * Get the most frequent state in this pattern.
+ * @return the most frequent state
+ */
+ public State getMostFrequentState() {
+ int maxCount = 0;
+ State mostFrequentState = null;
+ int[] counts = new int[sequenceType.getStateCount()];
+ for (State state : states) {
+ counts[state.getIndex()] += 1;
+ if (counts[state.getIndex()] > maxCount) {
+ maxCount = counts[state.getIndex()];
+ mostFrequentState = state;
+ }
+ }
+ return mostFrequentState;
+ }
+
+ public double getStateFrequency(State state) {
+ double count = 0;
+ for (State s : states) {
+ if (s == state) {
+ count += 1;
+ }
+ }
+ return count / states.size();
+ }
+
+ private final List<State> states;
+ }
+
+}
diff --git a/src/jebl/evolution/alignments/BootstrappedAlignment.java b/src/jebl/evolution/alignments/BootstrappedAlignment.java
new file mode 100644
index 0000000..81e8c31
--- /dev/null
+++ b/src/jebl/evolution/alignments/BootstrappedAlignment.java
@@ -0,0 +1,25 @@
+package jebl.evolution.alignments;
+
+import jebl.math.Random;
+
+/**
+ * Date: 15/01/2006
+ * Time: 10:13:50
+ *
+ * @author Joseph Heled
+ * @version $Id: BootstrappedAlignment.java 585 2006-12-15 15:48:59Z twobeers $
+ *
+ */
+public class BootstrappedAlignment extends ResampledAlignment {
+
+ public BootstrappedAlignment(Alignment srcAlignment) {
+ final int nSites = srcAlignment.getSiteCount();
+ int[] sites = new int[nSites];
+
+ for(int n = 0; n < nSites; ++n) {
+ sites[n] = Random.nextInt(nSites);
+ }
+
+ init(srcAlignment, sites);
+ }
+}
diff --git a/src/jebl/evolution/alignments/ConsensusSequence.java b/src/jebl/evolution/alignments/ConsensusSequence.java
new file mode 100644
index 0000000..b37c298
--- /dev/null
+++ b/src/jebl/evolution/alignments/ConsensusSequence.java
@@ -0,0 +1,151 @@
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author rambaut
+ * @author Alexei Drummond
+ * @version $Id: ConsensusSequence.java 365 2006-06-28 07:34:56Z pepster $
+ */
+public abstract class ConsensusSequence implements Sequence {
+ /**
+ * Creates a FilteredSequence wrapper to the given source sequence.
+ *
+ * @param source
+ */
+ public ConsensusSequence(Taxon taxon, Alignment source) {
+
+ this.taxon = taxon;
+ this.source = source;
+ }
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ public SequenceType getSequenceType() {
+ return source.getSequenceType();
+ }
+
+ /**
+ * @return a string representing the sequence of symbols.
+ */
+ public String getString() {
+ if (sequence == null) {
+ sequence = jebl.evolution.sequences.Utils.getStateIndices(constructConsensus(source));
+ }
+
+ SequenceType sequenceType = getSequenceType();
+ StringBuilder buffer = new StringBuilder();
+ for (int i : sequence) {
+ buffer.append(sequenceType.getState(i).getCode());
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * @return an array of state objects.
+ */
+ public State[] getStates() {
+ if (sequence == null) {
+ sequence = jebl.evolution.sequences.Utils.getStateIndices(constructConsensus(source));
+ }
+ return getSequenceType().toStateArray(sequence);
+ }
+
+ public byte[] getStateIndices() {
+ if (sequence == null) {
+ sequence = jebl.evolution.sequences.Utils.getStateIndices(constructConsensus(source));
+ }
+ return sequence;
+ }
+
+ /**
+ * @return the state at site.
+ */
+ public State getState(int site) {
+ if (sequence == null) {
+ sequence = jebl.evolution.sequences.Utils.getStateIndices(constructConsensus(source));
+ }
+ return getSequenceType().getState(sequence[site]);
+ }
+
+ /**
+ * Returns the length of the sequence
+ *
+ * @return the length
+ */
+ public int getLength() {
+ if (sequence == null) {
+ sequence = jebl.evolution.sequences.Utils.getStateIndices(constructConsensus(source));
+ }
+ return sequence.length;
+ }
+
+ public static State[] constructConsensus(Alignment source) {
+ State[] consensus = new State[source.getPatterns().size()];
+ int i = 0;
+ for (Pattern pattern : source.getPatterns()) {
+ consensus[i] = pattern.getMostFrequentState();
+ i++;
+ }
+
+ return consensus;
+ }
+
+ /**
+ * @return that taxon that this sequence represents (primarily used to match sequences with tree nodes)
+ */
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ /**
+ * Sequences are compared by their taxa
+ *
+ * @param o another sequence
+ * @return an integer
+ */
+ public int compareTo(Object o) {
+ return taxon.compareTo(((Sequence) o).getTaxon());
+ }
+
+ // Attributable implementation
+
+ public void setAttribute(String name, Object value) {
+ if (attributeMap == null) {
+ attributeMap = new HashMap<String, Object>();
+ }
+ attributeMap.put(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (attributeMap == null) {
+ return null;
+ }
+ return attributeMap.get(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ if (attributeMap == null) {
+ return Collections.emptySet();
+ }
+ return attributeMap.keySet();
+ }
+
+ // private members
+
+ private final Taxon taxon;
+ private final Alignment source;
+ private byte[] sequence = null;
+
+ private Map<String, Object> attributeMap = null;
+
+}
diff --git a/src/jebl/evolution/alignments/JackknifedAlignment.java b/src/jebl/evolution/alignments/JackknifedAlignment.java
new file mode 100644
index 0000000..7c549e4
--- /dev/null
+++ b/src/jebl/evolution/alignments/JackknifedAlignment.java
@@ -0,0 +1,29 @@
+package jebl.evolution.alignments;
+
+import jebl.math.Random;
+
+/**
+ * Date: 17/01/2006
+ * Time: 08:18:32
+ *
+ * @author Joseph Heled
+ * @version $Id: JackknifedAlignment.java 482 2006-10-25 06:30:57Z twobeers $
+ *
+ */
+public class JackknifedAlignment extends ResampledAlignment {
+ public JackknifedAlignment(Alignment srcAlignment, double percent) {
+ final int nSites = srcAlignment.getSiteCount();
+ final int nNewSites = (int)Math.ceil(nSites * percent);
+ int[] sites = new int[nSites];
+
+ for(int n = 0; n < nSites; ++n) {
+ sites[n] = n;
+ }
+
+ Random.shuffle(sites);
+
+ int[] newSites = new int[nNewSites];
+ System.arraycopy(sites, 0, newSites, 0, nNewSites);
+ init(srcAlignment, newSites);
+ }
+}
diff --git a/src/jebl/evolution/alignments/Pattern.java b/src/jebl/evolution/alignments/Pattern.java
new file mode 100644
index 0000000..b20b313
--- /dev/null
+++ b/src/jebl/evolution/alignments/Pattern.java
@@ -0,0 +1,76 @@
+/*
+ * Pattern.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An interface representing a list of states for a list of taxa
+ * (e.g. an alignment column).
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Pattern.java 658 2007-03-20 03:27:20Z twobeers $
+ */
+public interface Pattern {
+
+ /**
+ * @return the data type of the states in this pattern.
+ */
+ SequenceType getSequenceType();
+
+ int getLength();
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ List<Taxon> getTaxa();
+
+ /**
+ * Get the state for the ith taxon
+ * @param index
+ * @return the state
+ */
+ State getState(int index);
+
+ /**
+ * @return the list of state values of this pattern.
+ */
+ List<State> getStates();
+
+ /**
+ * @return the set of state values of this pattern.
+ */
+ Set<State> getStateSet();
+
+ /**
+ * Get the weight of this pattern
+ * @return the weight
+ */
+ double getWeight();
+
+ /**
+ * Returns the most frequent state in this pattern
+ * @return the most frequent state
+ */
+ State getMostFrequentState();
+
+ /**
+ * Returns the frequent of the given state in this pattern
+ * @param state
+ * @return the frequency
+ */
+ double getStateFrequency(State state);
+}
diff --git a/src/jebl/evolution/alignments/Patterns.java b/src/jebl/evolution/alignments/Patterns.java
new file mode 100644
index 0000000..017db50
--- /dev/null
+++ b/src/jebl/evolution/alignments/Patterns.java
@@ -0,0 +1,46 @@
+/*
+ * Patterns.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.List;
+
+/**
+ * An interface representing a set of site patterns.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Patterns.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public interface Patterns {
+
+ int getPatternCount();
+
+ int getPatternLength();
+
+ /**
+ * Get a list of all the patterns
+ * @return the list
+ */
+ List<Pattern> getPatterns();
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ List<Taxon> getTaxa();
+
+ /**
+ * @return the data type of the states in these site patterns.
+ */
+ SequenceType getSequenceType();
+
+}
diff --git a/src/jebl/evolution/alignments/ResampledAlignment.java b/src/jebl/evolution/alignments/ResampledAlignment.java
new file mode 100644
index 0000000..386bf75
--- /dev/null
+++ b/src/jebl/evolution/alignments/ResampledAlignment.java
@@ -0,0 +1,97 @@
+package jebl.evolution.alignments;
+
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Date: 17/01/2006
+ * Time: 08:08:44
+ *
+ * @author Joseph Heled
+ * @version $Id: ResampledAlignment.java 482 2006-10-25 06:30:57Z twobeers $
+ *
+ * Provide a re-sampled alignment. This means an alignment constructed by choosing a set of sites from
+ * the source alignment and concataneting them. The set may be of any length and may contain duplications
+ * (sampling with replacment).
+ *
+ * Due to Java restrictions on constructors, class is implemented using delegation.
+ */
+
+public class ResampledAlignment implements Alignment {
+ protected BasicAlignment alignment;
+
+ /**
+ * Setup resampled alignment.
+ *
+ * @param srcAlignment sample sites from this alignment
+ * @param siteIndices Use this set to construct the resampled alignment
+ */
+ public void init(Alignment srcAlignment, int[] siteIndices) {
+ final int nNewSites = siteIndices.length;
+ final int nSeqs = srcAlignment.getSequences().size();
+
+ // Work directly with states (fastest)
+ State[][] newSeqsStates = new State[nSeqs][];
+
+ for(int k = 0; k < nSeqs; ++k) {
+ newSeqsStates[k] = new State[nNewSites];
+ }
+
+ final List<Sequence> seqs = srcAlignment.getSequenceList();
+ for(int n = 0; n < nNewSites; ++n) {
+ final int fromSite = siteIndices[n];
+ for(int k = 0; k < nSeqs; ++k) {
+ newSeqsStates[k][n] = seqs.get(k).getState(fromSite);
+ }
+ }
+
+ Sequence[] newSeqs = new Sequence[nSeqs];
+ for(int k = 0; k < nSeqs; ++k) {
+ Sequence src = seqs.get(k);
+ newSeqs[k] = new BasicSequence(src.getSequenceType(), src.getTaxon(), newSeqsStates[k]);
+ }
+ alignment = new BasicAlignment(newSeqs);
+ }
+
+ public List<Sequence> getSequenceList() {
+ return alignment.getSequenceList();
+ }
+
+ public int getPatternCount() {
+ return alignment.getPatternCount();
+ }
+
+ public int getPatternLength() {
+ return alignment.getPatternLength();
+ }
+
+ public List<Pattern> getPatterns() {
+ return alignment.getPatterns();
+ }
+
+ public List<Taxon> getTaxa() {
+ return alignment.getTaxa();
+ }
+
+ public SequenceType getSequenceType() {
+ return alignment.getSequenceType();
+ }
+
+ public int getSiteCount() {
+ return alignment.getSiteCount();
+ }
+
+ public Set<Sequence> getSequences() {
+ return alignment.getSequences();
+ }
+
+ public Sequence getSequence(Taxon taxon) {
+ return alignment.getSequence(taxon);
+ }
+}
diff --git a/src/jebl/evolution/characterReconstruction/MLContinuousCharacterReconstructor.java b/src/jebl/evolution/characterReconstruction/MLContinuousCharacterReconstructor.java
new file mode 100644
index 0000000..7b7465d
--- /dev/null
+++ b/src/jebl/evolution/characterReconstruction/MLContinuousCharacterReconstructor.java
@@ -0,0 +1,245 @@
+package jebl.evolution.characterReconstruction;
+
+import java.util.*;
+
+import jebl.evolution.taxa.*;
+import jebl.evolution.trees.*;
+import jebl.evolution.graphs.*;
+import jebl.evolution.characters.ContinuousCharacter;
+import jebl.math.MatrixCalc;
+import jebl.math.MatrixCalcException;
+
+/**
+ * This is the main class that controls the ancestral calculations by the Schluter ML method
+ * The theoretical foundations are found in Schulter et al. 1997 Evolution.
+ * @author Stephen A. Smith
+ */
+
+public class MLContinuousCharacterReconstructor {
+ /**
+ * a basic constructor which just defines the tree and character
+ * @param conchar the continuous character to reconstruct
+ * @param tree the tree on which to reconstruct the character
+ */
+ public MLContinuousCharacterReconstructor(ContinuousCharacter conchar, SimpleRootedTree tree){
+ this.conchar = conchar;
+ this.tree = tree;
+ }
+
+ /**
+ * public
+ */
+ /**
+ * @param tree the tree for the analysis, unnecessary if using the same tree used from the constructor
+ */
+ public void setTree(SimpleRootedTree tree){
+ this.tree = tree;
+ }
+
+ /**
+ * @param chonchar the character for the analsysis, unnecessary if using the same tree used from the constructor
+ */
+ public void setChar(ContinuousCharacter conchar){
+ this.conchar = conchar;
+ }
+
+ /**
+ * initiates the analysis
+ */
+ public void reconstruct(){
+ phenotypes = new HashMap<Node, Double>();
+ phenotypesSE = new HashMap<Node, Double>();
+ internalNodeNums = new HashMap<Node, Integer>();
+ beginFill();
+ beginStats();
+ }
+
+ /**
+ * @return the phenotypes estimated from the analysis
+ */
+ public Map<Node, Double> getPhenotypes(){return phenotypes;}
+
+ /**
+ * @return the phenotype standard errors estimated from the analysis
+ */
+ public Map<Node, Double> getPhenotypesSE(){return phenotypesSE;}
+
+ /**
+ * private
+ */
+ /**
+ * initiate some variables and arrays that are used throughout the analysis
+ */
+ private void beginFill(){
+ //init some things to make the code a little easier
+ Object [] TinternalNodes = tree.getInternalNodes().toArray();
+ numInNodes = TinternalNodes.length;
+ internalNodes = new Node [numInNodes];
+ Object [] TccTaxa = conchar.getTaxa().toArray();
+ for(int i=0;i<TccTaxa.length;i++){
+ phenotypes.put(tree.getNode((Taxon)TccTaxa[i]),(Double)conchar.getValue((Taxon)TccTaxa[i]));
+ phenotypesSE.put(tree.getNode((Taxon)TccTaxa[i]),(Double)conchar.getSE((Taxon)TccTaxa[i]));
+ }
+ for(int i=0;i<numInNodes;i++){
+ internalNodes[i] = (Node)TinternalNodes[i];
+ phenotypes.put(internalNodes[i],0.0);
+ phenotypesSE.put(internalNodes[i],0.0);
+ internalNodeNums.put(internalNodes[i], i);
+ }
+ }
+
+ /**
+ * the meat of the analysis which calculates the estimates of both ML estimates for each node
+ * as well as the SE estimates for each node
+ */
+ private void beginStats(){
+ //MLE
+ double mlsos = getMLSofS(tree);
+ //estimate BETA
+ double df = numInNodes;//degrees of freedom, with one character this is num of tips - 1
+ double [] tempSE = new double [numInNodes];
+ for(int i=0; i < numInNodes; i++){
+ tempSE[i]= getSEest(i);
+ phenotypesSE.put(internalNodes[i],Math.sqrt(2*mlsos/(df*tempSE[i])));
+ }
+ }
+
+ /**
+ * evaluate ML residual sum of squares
+ */
+ private double getMLSofS(Tree tp) {
+ computeMLest();
+ //global qsum for ease
+ qsum = 0;
+ addToQ(tree.getRootNode()); //sum of squares
+ return qsum;
+ }
+
+ /**
+ * compute the ML estimates for all nodes
+ */
+ private void computeMLest(){
+ fullM = new double [numInNodes][numInNodes];
+ double [] fullRhs = new double [numInNodes];
+ fullMcp=fullM;
+ fullVcp=fullRhs;
+ // calculate q matrix
+ for(int i=0; i<numInNodes; i++){
+ Node p=internalNodes[i];
+ doQCalc(tree.getChildren(p).get(0),i);
+ doQCalc(tree.getChildren(p).get(1),i);
+ }
+ //save a copy for SE calculation
+ SEfullM = MatrixCalc.copyMatrix(fullM);
+ //Cholesky factorization and solution
+ double [][] CHfact=fullM;
+ try{
+ CHfact=MatrixCalc.choleskyFactor(CHfact);
+ }catch(MatrixCalcException.NotSquareMatrixException npe){}
+ catch (MatrixCalcException.PositiveDefiniteException pde){};
+ double [] rhs=fullRhs;
+ double [] mle = null;
+ try{
+ mle=MatrixCalc.choleskySolve(CHfact,rhs);
+ }catch(MatrixCalcException.NotSquareMatrixException npe){};
+ // set values at nodes
+ for(int i=0; i<numInNodes; i++){
+ Node p=internalNodes[i];
+ phenotypes.put(p,mle[i]);
+ }
+ }
+
+ /**
+ * compute and return the standard error estimates for a particular node
+ */
+ private double getSEest(int nodeNum){
+ int ir = nodeNum;
+ double qpp = SEfullM[ir][ir];
+ double [][] oneLessRow = MatrixCalc.deleteMatrixRow(SEfullM,ir);
+ double [] grabDcol = MatrixCalc.getColumn(oneLessRow,ir);
+ double [][] reducedM = MatrixCalc.deleteMatrixColumn(oneLessRow,ir);
+ try{
+ reducedM = MatrixCalc.choleskyFactor(reducedM);
+ }catch(MatrixCalcException.NotSquareMatrixException npe){}
+ catch (MatrixCalcException.PositiveDefiniteException pde){};
+ double [] CHsol = null;
+ try{
+ CHsol = MatrixCalc.choleskySolve(reducedM,grabDcol);
+ }catch(MatrixCalcException.NotSquareMatrixException npe){};
+ double tempSE = qpp - MatrixCalc.innerProduct(grabDcol,CHsol,0);
+ return tempSE;
+ }
+
+ /**
+ * add in values corresponding to one node
+ */
+ private void doQCalc(Node q,int ip){
+ double tbl=2/tree.getLength(q);
+ fullMcp[ip][ip] += tbl;
+ if(tree.isExternal(q)){
+ fullVcp[ip] += phenotypes.get(q)*tbl;
+ }
+ else {
+ int iq=internalNodeNums.get(q);
+ fullMcp[ip][iq] -= tbl;
+ fullMcp[iq][ip] -= tbl;
+ fullMcp[iq][iq] += tbl;
+ }
+ }
+
+ private void addToQ(Node p){
+ if(tree.isExternal(p)) return;
+ addToQ(tree.getChildren(p).get(0));
+ addToQ(tree.getChildren(p).get(1));
+ twoQBL(p,tree.getChildren(p).get(0));
+ twoQBL(p,tree.getChildren(p).get(1));
+ }
+
+ private void twoQBL(Node p, Node q){
+ double temp = phenotypes.get(p) - phenotypes.get(q);
+ qsum += temp*temp / tree.getLength(q);
+ }
+
+ private int numInNodes;
+ /**
+ * internal node array
+ */
+ private Node [] internalNodes;
+ /**
+ * could use the above numbers, but figured this method was more stable
+ */
+ private Map<Node, Integer> internalNodeNums;
+ private double qsum;
+ /**
+ * all the working phenotypes
+ */
+ private double [][] fullM;
+ /**
+ * full matrix copy used in q calculation
+ */
+ private double [][] fullMcp;
+ /**
+ * full vector copy
+ */
+ private double [] fullVcp;
+ /**
+ * saved full_mat which will be used for calculating the SE
+ */
+ private double [][] SEfullM;
+ /**
+ * final phenotypes
+ */
+ private Map<Node, Double> phenotypes;
+ /**
+ * final phenotype standard errors
+ */
+ private Map<Node, Double> phenotypesSE;
+ /**
+ * tree
+ */
+ private SimpleRootedTree tree;
+ /**
+ * character
+ */
+ private ContinuousCharacter conchar;
+}
diff --git a/src/jebl/evolution/characters/Character.java b/src/jebl/evolution/characters/Character.java
new file mode 100644
index 0000000..08f1e0b
--- /dev/null
+++ b/src/jebl/evolution/characters/Character.java
@@ -0,0 +1,60 @@
+package jebl.evolution.characters;
+
+import jebl.evolution.taxa.*;
+import java.util.*;
+
+/**
+ * @author Stephen A. Smith
+ *
+ */
+public interface Character{
+
+ /**
+ * set the name of the character
+ * @param name the name of the character
+ */
+ public void setName(String name);
+
+ /**
+ * return the name of the character
+ * @return the name of the character
+ */
+ public String getName();
+
+ /**
+ * set the description of the character
+ * @param desc the description of the character
+ */
+ public void setDesc(String desc);
+
+ /**
+ * return the description of the character
+ * @return the description of the character
+ */
+ public String getDesc();
+
+ /**
+ * return the CharacterType of the character
+ * @return the CharacterType of the character
+ */
+ public CharacterType getType();
+
+ /**
+ * add a taxon with this character
+ * @param taxon the taxon to add containing the character
+ */
+ public void addTaxon(Taxon taxon);
+
+ /**
+ * get a value for a taxon containing the character
+ * @param taxon the taxon to get the value for
+ * @return the Object value of the character for the given taxon
+ */
+ public Object getValue(Taxon taxon);
+
+ /**
+ * get a Set<Taxon> of all the taxa for this character
+ * @return a Set<Taxon> containing all of the taxa for this character
+ */
+ public Set<Taxon> getTaxa();
+}
diff --git a/src/jebl/evolution/characters/CharacterType.java b/src/jebl/evolution/characters/CharacterType.java
new file mode 100644
index 0000000..bb3dbb9
--- /dev/null
+++ b/src/jebl/evolution/characters/CharacterType.java
@@ -0,0 +1,17 @@
+package jebl.evolution.characters;
+
+/**
+ * @author Stephen A. Smith
+ *
+ */
+public interface CharacterType {
+ String getName();
+
+ public static final CharacterType DISCRETE = new CharacterType() {
+ public String getName(){ return "DISCRETE"; }
+ };
+
+ public static final CharacterType CONTINUOUS = new CharacterType() {
+ public String getName(){ return "CONTINUOUS"; }
+ };
+}
diff --git a/src/jebl/evolution/characters/ContinuousCharacter.java b/src/jebl/evolution/characters/ContinuousCharacter.java
new file mode 100644
index 0000000..2c362b1
--- /dev/null
+++ b/src/jebl/evolution/characters/ContinuousCharacter.java
@@ -0,0 +1,89 @@
+package jebl.evolution.characters;
+
+import java.util.*;
+
+import jebl.evolution.taxa.*;
+
+/**
+ * @author Stephen A. Smith
+ *
+ */
+
+public class ContinuousCharacter implements Character{
+ /**
+ * Constructs a basic ContinuousCharacter object with no taxa added yet
+ * @param name the name of the character
+ * @param desc the description of the character
+ */
+ public ContinuousCharacter(String name, String desc) {
+ this.name = name;
+ this.charType = CharacterType.CONTINUOUS;
+ this.desc = desc;
+ taxa = new HashSet <Taxon> ();
+ }
+
+ /**
+ * Constructs a basic ContinuousCharacter object with taxa added
+ * @param name the name of the character
+ * @param desc the description of the character
+ * @param taxa the Set<Taxon> containing the taxa
+ */
+ public ContinuousCharacter(String name, String desc, Set<Taxon> taxa) {
+ this.name = name;
+ this.charType = CharacterType.CONTINUOUS;
+ this.desc = desc;
+ this.taxa = taxa;
+ }
+
+ public void setName(String name){
+ this.name = name;
+ }
+
+ public String getName(){ return name; }
+
+ public void setDesc(String desc){
+ this.desc = desc;
+ }
+
+ public String getDesc(){ return desc; }
+
+ public CharacterType getType(){
+ return charType;
+ }
+
+ /**
+ * set the taxa for this character with a previously constructed Set<Taxon>
+ * @param taxa a Set<Taxon> of the taxa containing this character
+ */
+ public void addTaxa(Set<Taxon>taxa){
+ this.taxa = taxa;
+ }
+
+ public void addTaxon(Taxon taxon){
+ taxa.add(taxon);
+ }
+
+ public Object getValue(Taxon taxon){
+ double value = ((Double)taxon.getAttribute(name)).doubleValue();
+ return value;
+ }
+
+ /**
+ *
+ * @param taxon the taxon for which to get the standard error
+ * @return double of the standard error for the taxon
+ */
+ public double getSE(Taxon taxon){
+ double value = 0.0;
+ if(taxon.getAttribute(name+"SE") != null)
+ value = ((Double)taxon.getAttribute(name+"SE")).doubleValue();
+ return value;
+ }
+
+ public Set<Taxon> getTaxa(){ return taxa; }
+
+ private String desc;
+ private String name;
+ private CharacterType charType;
+ private Set<Taxon> taxa;
+}
diff --git a/src/jebl/evolution/characters/DiscreteCharacter.java b/src/jebl/evolution/characters/DiscreteCharacter.java
new file mode 100644
index 0000000..b2083b5
--- /dev/null
+++ b/src/jebl/evolution/characters/DiscreteCharacter.java
@@ -0,0 +1,125 @@
+package jebl.evolution.characters;
+
+import java.util.*;
+
+import jebl.evolution.taxa.*;
+
+/**
+ * @author Stephen A. Smith
+ *
+ */
+public class DiscreteCharacter implements Character{
+
+ /**
+ * Constructs a basic DiscreteCharacter object with no taxa added yet
+ * @param name the name of the character
+ * @param desc the description of the character
+ * @param numOfStates the number of possible states for the character
+ */
+ public DiscreteCharacter(String name, String desc, int numOfStates) {
+ this.name = name;
+ this.charType = CharacterType.DISCRETE;
+ this.desc = desc;
+ this.numOfStates = numOfStates;
+ this.taxa = new HashSet <Taxon> ();
+ }
+ /**
+ * Constructs a basic DiscreteCharacter object with taxa
+ * @param name the name of the character
+ * @param desc the description of the character
+ * @param numOfStates the number of possible states for the character
+ * @param taxa the Set<Taxon> containing the taxa with this character
+ */
+ public DiscreteCharacter(String name, String desc, int numOfStates, Set<Taxon> taxa) {
+ this.name = name;
+ this.charType = CharacterType.DISCRETE;
+ this.desc = desc;
+ this.numOfStates = numOfStates;
+ this.taxa = taxa;
+ }
+
+ public void setName(String name){
+ this.name = name;
+ }
+
+ public String getName(){ return name; }
+
+ public void setDesc(String desc){
+ this.desc = desc;
+ }
+
+ public String getDesc(){ return desc; }
+
+ public CharacterType getType(){
+ return charType;
+ }
+
+ public void addTaxon(Taxon taxon){
+ taxa.add(taxon);
+ }
+
+ public Object getValue(Taxon taxon){
+ int value = ((Integer)taxon.getAttribute(name)).intValue();
+ return value;
+ }
+
+ /**
+ * @return whether character is ordered or not
+ */
+ public boolean isOrdered(){ return isOrdered;}
+
+ /**
+ *
+ * @param isOrdered set whether character is ordered or not
+ */
+ public void setIsOrdered(boolean isOrdered){
+ this.isOrdered = isOrdered;
+ }
+
+ /**
+ *
+ * @return the number of possible states for the character
+ */
+ public double getNumOfStates(){ return numOfStates; }
+
+ /**
+ *
+ * @param numOfStates the number of possible states for the characeter
+ */
+ public void setNumOfStates(int numOfStates){
+ this.numOfStates = numOfStates;
+ }
+
+ public Set<Taxon> getTaxa(){ return taxa; }
+
+ /**
+ *
+ * @param stateDesc a Map<Integer, String> of the state descriptions corresponding to the values
+ */
+ public void setStateDesc(Map <Integer, String> stateDesc){
+ this.stateDesc = stateDesc;
+ }
+
+ /**
+ *
+ * @return the Map<Integer, String> of the state descriptions corresponding to the values
+ */
+ public Map <Integer, String> getStateDesc(){ return stateDesc; }
+
+ /**
+ *
+ * @param state corresponding to the state
+ * @return state description
+ */
+ public String getStateDesc(int state){
+ return stateDesc.get(state);
+ }
+
+ private boolean isOrdered;
+ private String name;
+ private String desc;
+ private CharacterType charType;
+ private Set<Taxon> taxa;
+ private Map<Integer, String> stateDesc;
+ private int numOfStates;
+}
diff --git a/src/jebl/evolution/coalescent/CataclysmicDemographic.java b/src/jebl/evolution/coalescent/CataclysmicDemographic.java
new file mode 100644
index 0000000..ee7380b
--- /dev/null
+++ b/src/jebl/evolution/coalescent/CataclysmicDemographic.java
@@ -0,0 +1,132 @@
+/*
+ * CataclysmicDemographic.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models an exponentially growing (or shrinking) population
+ * (Parameters: N0=present-day population size; r=growth rate).
+ * This model is nested with the constant-population size model (r=0).
+ *
+ * @version $Id: CataclysmicDemographic.java 586 2006-12-15 15:49:15Z twobeers $
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ */
+public class CataclysmicDemographic extends ExponentialGrowth {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public CataclysmicDemographic() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ * @param N0 present-day population size
+ * @param r growth rate
+ */
+ public CataclysmicDemographic(double N0, double r, double d, double t) {
+
+ super(N0, r);
+ this.d = d;
+ this.catTime = t;
+ }
+
+ /**
+ * returns the positive-valued decline rate
+ */
+ public final double getDeclineRate() { return d; }
+
+ /**
+ * sets the decline rate.
+ */
+ public void setDeclineRate(double d) {
+ if (d <= 0) throw new IllegalArgumentException();
+ this.d = d;
+ }
+
+ public final double getCataclysmTime() { return catTime; }
+
+ public final void setCataclysmTime(double t) {
+ if (t <= 0) throw new IllegalArgumentException();
+ catTime = t;
+ }
+
+ /**
+ * An alternative parameterization of this model. This
+ * function sets the decline rate using N0 & t which must
+ * already have been set.
+ */
+ public final void setSpikeFactor(double f) {
+ setDeclineRate( Math.log(f) / catTime );
+ }
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) {
+
+ double d = getDeclineRate();
+
+ if (t < catTime) {
+ return getN0() * Math.exp(t * d);
+ } else {
+ double spikeHeight = getN0() * Math.exp(catTime * d);
+ //System.out.println("Spike height = " + spikeHeight);
+ t -= catTime;
+
+ double r = getGrowthRate();
+ if (r == 0) {
+ return spikeHeight;
+ } else {
+ return spikeHeight * Math.exp(-t * r);
+ }
+ }
+ }
+
+ public double getIntensity(double t) {
+
+ double d = getDeclineRate();
+ double r = getGrowthRate();
+ if (t < catTime) {
+ return (Math.exp(t*-d)-1.0)/getN0()/-d;
+ } else {
+
+ double intensityUpToSpike = (Math.exp(catTime*-d)-1.0)/getN0()/-d;
+
+ double spikeHeight = getN0() * Math.exp(catTime * d);
+ t -= catTime;
+ //System.out.println("Spike height = " + spikeHeight);
+
+ if (r == 0) {
+ return t/spikeHeight + intensityUpToSpike;
+ } else {
+ return (Math.exp(t*r)-1.0)/spikeHeight/r + intensityUpToSpike;
+ }
+ }
+ }
+
+ public double getInverseIntensity(double x) {
+
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasIntegral() {
+ return false;
+ }
+
+
+ //
+ // private stuff
+ //
+
+ private double d;
+ private double catTime;
+}
diff --git a/src/jebl/evolution/coalescent/Coalescent.java b/src/jebl/evolution/coalescent/Coalescent.java
new file mode 100644
index 0000000..deedebc
--- /dev/null
+++ b/src/jebl/evolution/coalescent/Coalescent.java
@@ -0,0 +1,155 @@
+/*
+ * Coalescent.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+import jebl.math.*;
+import jebl.evolution.trees.RootedTree;
+
+/**
+ * A likelihood function for the coalescent. Takes a tree and a demographic model.
+ *
+ * Parts of this class were derived from C++ code provided by Oliver Pybus.
+ *
+ * @version $Id: Coalescent.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ */
+public class Coalescent implements MultivariateFunction {
+
+ // PUBLIC STUFF
+
+ public Coalescent(RootedTree tree, DemographicFunction demographicFunction) {
+ this(new Intervals(tree), demographicFunction);
+ }
+
+ public Coalescent(IntervalList intervals, DemographicFunction demographicFunction) {
+
+ this.intervals = intervals;
+ this.demographicFunction = demographicFunction;
+ }
+
+
+ /**
+ * Calculates the log likelihood of this set of coalescent intervals,
+ * given a demographic model.
+ */
+ public double calculateLogLikelihood() {
+
+ return calculateLogLikelihood(intervals, demographicFunction);
+ }
+
+ /**
+ * Calculates the log likelihood of this set of coalescent intervals,
+ * given a demographic model.
+ */
+ public static final double calculateLogLikelihood(IntervalList intervals,
+ DemographicFunction demographicFunction) {
+
+ double logL = 0.0;
+
+ double startTime = 0.0;
+
+ for (int i = 0, n = intervals.getIntervalCount(); i < n; i++) {
+
+ double duration = intervals.getInterval(i);
+ double finishTime = startTime + duration;
+
+ double intervalArea = demographicFunction.getIntegral(startTime, finishTime);
+ int lineageCount = intervals.getLineageCount(i);
+
+ if (intervals.getIntervalType(i) == IntervalList.IntervalType.COALESCENT) {
+
+ logL += -Math.log(demographicFunction.getDemographic(finishTime)) -
+ (Binomial.choose2(lineageCount)*intervalArea);
+
+ } else { // SAMPLE or NOTHING
+
+ logL += -(Binomial.choose2(lineageCount)*intervalArea);
+ }
+
+ startTime = finishTime;
+ }
+
+ return logL;
+ }
+
+ /**
+ * Calculates the log likelihood of this set of coalescent intervals,
+ * using an analytical integration over theta.
+ */
+ public static final double calculateAnalyticalLogLikelihood(IntervalList intervals) {
+
+ if (!intervals.isCoalescentOnly()) {
+ throw new IllegalArgumentException("Can only calculate analytical likelihood for pure coalescent intervals");
+ }
+
+ double lambda = getLambda(intervals);
+ int n = intervals.getSampleCount();
+
+ double logL = 0.0;
+
+ // assumes a 1/theta prior
+ //logLikelihood = Math.log(1.0/Math.pow(lambda,n));
+
+ // assumes a flat prior
+ logL = Math.log(1.0/Math.pow(lambda,n-1));
+ return logL;
+ }
+
+ /**
+ * Returns a factor lambda such that the likelihood can be expressed as
+ * 1/theta^(n-1) * exp(-lambda/theta). This allows theta to be integrated
+ * out analytically. :-)
+ */
+ private static final double getLambda(IntervalList intervals) {
+ double lambda = 0.0;
+ for (int i= 0; i < intervals.getIntervalCount(); i++) {
+ lambda += (intervals.getInterval(i) * intervals.getLineageCount(i));
+ }
+ lambda /= 2;
+
+ return lambda;
+ }
+
+ // **************************************************************
+ // MultivariateFunction IMPLEMENTATION
+ // **************************************************************
+
+ public double evaluate(double[] argument) {
+ for (int i = 0; i < argument.length; i++) {
+ demographicFunction.setArgument(i, argument[i]);
+ }
+
+ return calculateLogLikelihood();
+ }
+
+ public int getNumArguments() {
+ return demographicFunction.getArgumentCount();
+ }
+
+ public double getLowerBound(int n) {
+ return demographicFunction.getLowerBound(n);
+ }
+
+ public double getUpperBound(int n) {
+ return demographicFunction.getUpperBound(n);
+ }
+
+ public OrthogonalHints getOrthogonalHints() {
+ return null;
+ }
+
+ /** The demographic function. */
+ private final DemographicFunction demographicFunction;
+
+ /** The intervals. */
+ private final IntervalList intervals;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/coalescent/ConstExponential.java b/src/jebl/evolution/coalescent/ConstExponential.java
new file mode 100644
index 0000000..bcd0763
--- /dev/null
+++ b/src/jebl/evolution/coalescent/ConstExponential.java
@@ -0,0 +1,149 @@
+/*
+ * ConstExponential.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models exponential growth from an initial population size.
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ *
+ * @version $Id: ConstExponential.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ */
+public class ConstExponential extends ExponentialGrowth {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public ConstExponential() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public ConstExponential(double N0, double r, double N1) {
+
+ super(N0, r);
+ this.N1 = N1;
+ }
+
+ public double getN1() { return N1; }
+ public void setN1(double N1) { this.N1 = N1; }
+
+ public void setProportion(double p) { this.N1 = getN0() * p; }
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) {
+
+ double N0 = getN0();
+ double N1 = getN1();
+ double r = getGrowthRate();
+
+ //return nOne + ((nZero - nOne) * Math.exp(-r*t));
+
+ double time = Math.log(N0/N1)/r;
+
+ if (t < time) return N0 * Math.exp(-r*t);
+
+ return N1;
+ }
+
+ public double getIntensity(double t) {
+ double r = getGrowthRate();
+ double time = Math.log(getN0()/getN1())/r;
+
+ if (r == 0.0) return t/getN0();
+
+ if (t < time) {
+ return super.getIntensity(t);
+ } else {
+ return super.getIntensity(time) + (t-time)/getN1();
+ }
+ }
+
+ public double getInverseIntensity(double x) {
+ /* AER - I think this is right but until someone checks it...
+ double nZero = getN0();
+ double nOne = getN1();
+ double r = getGrowthRate();
+
+ if (r == 0) {
+ return nZero*x;
+ } else if (alpha == 0) {
+ return Math.log(1.0+nZero*x*r)/r;
+ } else {
+ return Math.log(-(nOne/nZero) + Math.exp(nOne*x*r))/r;
+ }
+ */
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasIntegral() {
+ return false;
+ }
+
+ public double getIntegral(double start, double finish) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return 3;
+ }
+
+ @Override
+ public String getArgumentName(int n) {
+ switch (n) {
+ case 0: return "N0";
+ case 1: return "r";
+ case 2: return "N1";
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public double getArgument(int n) {
+ switch (n) {
+ case 0: return getN0();
+ case 1: return getGrowthRate();
+ case 2: return getN1();
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public void setArgument(int n, double value) {
+ switch (n) {
+ case 0: setN0(value); break;
+ case 1: setGrowthRate(value); break;
+ case 2: setN1(value); break;
+ default: throw new IllegalArgumentException("Argument " + n + " does not exist");
+
+ }
+ }
+
+ @Override
+ public double getLowerBound(int n) {
+ return 0.0;
+ }
+
+ @Override
+ public double getUpperBound(int n) {
+ return Double.POSITIVE_INFINITY;
+ }
+
+ // private stuff
+ //
+
+ private double N1 = 0.0;
+}
diff --git a/src/jebl/evolution/coalescent/ConstLogistic.java b/src/jebl/evolution/coalescent/ConstLogistic.java
new file mode 100644
index 0000000..a0afefe
--- /dev/null
+++ b/src/jebl/evolution/coalescent/ConstLogistic.java
@@ -0,0 +1,123 @@
+// ConstLogistic.java
+//
+// (c) 2002-2004 BEAST Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models logistic growth from an initial population size.
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ *
+ * @version $Id: ConstLogistic.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ */
+public class ConstLogistic extends LogisticGrowth {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public ConstLogistic() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public ConstLogistic(double N0, double r, double c, double N1) {
+
+ super(N0, r, c);
+ this.N1 = N1;
+ }
+
+ public double getN1() { return N1; }
+ public void setN1(double N1) { this.N1 = N1; }
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) {
+
+ double nZero = getN0();
+ double nOne = getN1();
+ double r = getGrowthRate();
+ double c = getShape();
+
+ double common = Math.exp(-r*t);
+ return nOne + ((nZero - nOne) * (1 + c) * common) / (c + common);
+ }
+
+ public double getIntensity(double t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public double getInverseIntensity(double x) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasIntegral() {
+ return false;
+ }
+
+ public double getIntegral(double start, double finish) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return 4;
+ }
+
+ @Override
+ public String getArgumentName(int n) {
+ switch (n) {
+ case 0: return "N0";
+ case 1: return "r";
+ case 2: return "c";
+ case 3: return "N1";
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public double getArgument(int n) {
+ switch (n) {
+ case 0: return getN0();
+ case 1: return getGrowthRate();
+ case 2: return getShape();
+ case 3: return getN1();
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public void setArgument(int n, double value) {
+ switch (n) {
+ case 0: setN0(value); break;
+ case 1: setGrowthRate(value); break;
+ case 2: setShape(value); break;
+ case 3: setN1(value); break;
+ default: throw new IllegalArgumentException("Argument " + n + " does not exist");
+
+ }
+ }
+
+ @Override
+ public double getLowerBound(int n) {
+ return 0.0;
+ }
+
+ @Override
+ public double getUpperBound(int n) {
+ return Double.POSITIVE_INFINITY;
+ }
+
+ //
+ // private stuff
+ //
+
+ private double N1 = 0.0;
+}
diff --git a/src/jebl/evolution/coalescent/ConstantPopulation.java b/src/jebl/evolution/coalescent/ConstantPopulation.java
new file mode 100644
index 0000000..82b716c
--- /dev/null
+++ b/src/jebl/evolution/coalescent/ConstantPopulation.java
@@ -0,0 +1,104 @@
+/*
+ * ConstantPopulation.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models coalescent intervals for a constant population
+ * (parameter: N0=present-day population size). <BR>
+ * If time units are set to Units.EXPECTED_SUBSTITUTIONS then
+ * the N0 parameter will be interpreted as N0 * mu. <BR>
+ * Also note that if you are dealing with a diploid population
+ * N0 will be out by a factor of 2.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: ConstantPopulation.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ */
+public class ConstantPopulation implements DemographicFunction
+{
+ //
+ // Public stuff
+ //
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public ConstantPopulation() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public ConstantPopulation(double N0) {
+ this.N0 = N0;
+ }
+
+ /**
+ * returns initial population size.
+ */
+ public double getN0() { return N0; }
+
+ /**
+ * sets initial population size.
+ */
+ public void setN0(double N0) { this.N0 = N0; }
+
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) { return getN0(); }
+ public double getIntensity(double t) { return t/getN0(); }
+ public double getInverseIntensity(double x) { return getN0()*x; }
+
+ public boolean hasIntegral() {
+ return true;
+ }
+
+ /**
+ * Calculates the integral 1/N(x) dx between start and finish. The
+ * inherited function in DemographicFunction.Abstract calls a
+ * numerical integrater which is unecessary.
+ */
+ public double getIntegral(double start, double finish) {
+ return getIntensity(finish) - getIntensity(start);
+ }
+
+ public int getArgumentCount() {
+ return 1;
+ }
+
+ public String getArgumentName(int n) {
+ return "N0";
+ }
+
+ public double getArgument(int n) {
+ return getN0();
+ }
+
+ public void setArgument(int n, double value) {
+ setN0(value);
+ }
+
+ public double getLowerBound(int n) {
+ return 0.0;
+ }
+
+ public double getUpperBound(int n) {
+ return Double.POSITIVE_INFINITY;
+ }
+ //
+ // private stuff
+ //
+
+ private double N0;
+}
diff --git a/src/jebl/evolution/coalescent/DemographicFunction.java b/src/jebl/evolution/coalescent/DemographicFunction.java
new file mode 100644
index 0000000..f0699aa
--- /dev/null
+++ b/src/jebl/evolution/coalescent/DemographicFunction.java
@@ -0,0 +1,114 @@
+/*
+ * DemographicFunction.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This interface provides methods that describe a demographic function.
+ *
+ * Parts of this class were derived from C++ code provided by Oliver Pybus.
+ *
+ * @version $Id: DemographicFunction.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Korbinian Strimmer
+ */
+public interface DemographicFunction {
+
+ /**
+ * Gets the value of the demographic function N(t) at time t.
+ */
+ double getDemographic(double t);
+
+ /**
+ * Returns value of demographic intensity function at time t
+ * (= integral 1/N(x) dx from 0 to t).
+ */
+ double getIntensity(double t);
+
+ /**
+ * Returns value of inverse demographic intensity function
+ * (returns time, needed for simulation of coalescent intervals).
+ */
+ double getInverseIntensity(double x);
+
+ /**
+ * returns whether an analytical expression for the integral is implemented
+ * @return a boolean
+ */
+ boolean hasIntegral();
+
+ /**
+ * Calculates the integral 1/N(x) dx between start and finish
+ */
+ double getIntegral(double start, double finish);
+
+ /**
+ * Returns the number of arguments for this function.
+ */
+ int getArgumentCount();
+
+ /**
+ * Returns the name of the nth argument of this function.
+ */
+ String getArgumentName(int n);
+
+ /**
+ * Returns the value of the nth argument of this function.
+ */
+ double getArgument(int n);
+
+ /**
+ * Sets the value of the nth argument of this function.
+ */
+ void setArgument(int n, double value);
+
+ /**
+ * Returns the lower bound of the nth argument of this function.
+ */
+ double getLowerBound(int n);
+
+ /**
+ * Returns the upper bound of the nth argument of this function.
+ */
+ double getUpperBound(int n);
+
+ public static class Utils
+ {
+
+ /**
+ * This function tests the consistency of the
+ * getIntensity and getInverseIntensity methods
+ * of this demographic model. If the model is
+ * inconsistent then a RuntimeException will be thrown.
+ * @param demographicFunction the demographic model to test.
+ * @param steps the number of steps between 0.0 and maxTime to test.
+ * @param maxTime the maximum time to test.
+ */
+ public static void testConsistency(DemographicFunction demographicFunction, int steps, double maxTime) {
+
+ double delta = maxTime / (double)steps;
+
+ for (int i =0; i <= steps; i++) {
+ double time = (double)i * delta;
+ double intensity = demographicFunction.getIntensity(time);
+ double newTime = demographicFunction.getInverseIntensity(intensity);
+
+ if (Math.abs(time-newTime) > 1e-12) {
+ throw new RuntimeException(
+ "Demographic model not consistent! error size = " +
+ Math.abs(time-newTime));
+ }
+ }
+ }
+ };
+
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/coalescent/Expansion.java b/src/jebl/evolution/coalescent/Expansion.java
new file mode 100644
index 0000000..ac1639c
--- /dev/null
+++ b/src/jebl/evolution/coalescent/Expansion.java
@@ -0,0 +1,125 @@
+/*
+ * Expansion.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models exponential growth from an initial ancestral population size.
+ * (Parameters: N0=present-day population size; N1=ancestral population size; r=growth rate).
+ * This model is nested with the exponential-growth population size model (N1=0).
+ *
+ * @version $Id: Expansion.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ */
+public class Expansion extends ExponentialGrowth {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public Expansion() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public Expansion(double N0, double r, double N1) {
+
+ super(N0, r);
+ this.N1 = N1;
+ }
+
+ public double getN1() { return N1; }
+ public void setN1(double N1) { this.N1 = N1; }
+
+ public void setProportion(double p) { this.N1 = getN0() * p; }
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) {
+
+ double N0 = getN0();
+ double N1 = getN1();
+ double r = getGrowthRate();
+
+ assert (N1 > N0);
+
+ return N1 + ((N0 - N1) * Math.exp(-r*t));
+ }
+
+ public double getIntensity(double t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public double getInverseIntensity(double x) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasIntegral() {
+ return false;
+ }
+
+ public double getIntegral(double start, double finish) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return 3;
+ }
+
+ @Override
+ public String getArgumentName(int n) {
+ switch (n) {
+ case 0: return "N0";
+ case 1: return "r";
+ case 2: return "N1";
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public double getArgument(int n) {
+ switch (n) {
+ case 0: return getN0();
+ case 1: return getGrowthRate();
+ case 2: return getN1();
+ }
+ throw new IllegalArgumentException("Argument " + n + " does not exist");
+ }
+
+ @Override
+ public void setArgument(int n, double value) {
+ switch (n) {
+ case 0: setN0(value); break;
+ case 1: setGrowthRate(value); break;
+ case 2: setN1(value); break;
+ default: throw new IllegalArgumentException("Argument " + n + " does not exist");
+
+ }
+ }
+
+ @Override
+ public double getLowerBound(int n) {
+ return 0.0;
+ }
+
+ @Override
+ public double getUpperBound(int n) {
+ return Double.POSITIVE_INFINITY;
+ }
+
+ //
+ // private stuff
+ //
+
+ private double N1 = 0.0;
+}
diff --git a/src/jebl/evolution/coalescent/ExponentialGrowth.java b/src/jebl/evolution/coalescent/ExponentialGrowth.java
new file mode 100644
index 0000000..c15e52f
--- /dev/null
+++ b/src/jebl/evolution/coalescent/ExponentialGrowth.java
@@ -0,0 +1,137 @@
+/*
+ * ExponentialGrowth.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models an exponentially growing (or shrinking) population
+ * (Parameters: N0=present-day population size; r=growth rate).
+ * This model is nested with the constant-population size model (r=0).
+ *
+ * @version $Id: ExponentialGrowth.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ */
+public class ExponentialGrowth extends ConstantPopulation {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public ExponentialGrowth() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public ExponentialGrowth(double N0, double r) {
+
+ super(N0);
+ this.r = r;
+ }
+
+ /**
+ * returns growth rate.
+ */
+ public final double getGrowthRate() { return r; }
+
+ /**
+ * sets growth rate.
+ */
+ public void setGrowthRate(double r) { this.r = r; }
+
+ /**
+ * An alternative parameterization of this model. This
+ * function sets growth rate for a given doubling time.
+ */
+ public void setDoublingTime(double doublingTime) {
+ setGrowthRate( Math.log(2) / doublingTime );
+ }
+
+
+ // Implementation of abstract methods
+
+ public double getDemographic(double t) {
+
+ double r = getGrowthRate();
+ if (r == 0) {
+ return getN0();
+ } else {
+ return getN0() * Math.exp(-t * r);
+ }
+ }
+
+ public double getIntensity(double t)
+ {
+ double r = getGrowthRate();
+ if (r == 0.0) {
+ return t/getN0();
+ } else {
+ return (Math.exp(t*r)-1.0)/getN0()/r;
+ }
+ }
+
+ public double getInverseIntensity(double x) {
+
+ double r = getGrowthRate();
+ if (r == 0.0) {
+ return getN0()*x;
+ } else {
+ return Math.log(1.0+getN0()*x*r)/r;
+ }
+ }
+
+ @Override
+ public int getArgumentCount() {
+ return 2;
+ }
+
+ @Override
+ public String getArgumentName(int n) {
+ if (n == 0) {
+ return "N0";
+ } else {
+ return "r";
+ }
+ }
+
+ @Override
+ public double getArgument(int n) {
+ if (n == 0) {
+ return getN0();
+ } else {
+ return getGrowthRate();
+ }
+ }
+
+ @Override
+ public void setArgument(int n, double value) {
+ if (n == 0) {
+ setN0(value);
+ } else {
+ setGrowthRate(value);
+ }
+ }
+
+ @Override
+ public double getLowerBound(int n) {
+ return 0.0;
+ }
+
+ @Override
+ public double getUpperBound(int n) {
+ return Double.POSITIVE_INFINITY;
+ }
+ //
+ // private stuff
+ //
+
+ private double r;
+}
diff --git a/src/jebl/evolution/coalescent/IntervalList.java b/src/jebl/evolution/coalescent/IntervalList.java
new file mode 100644
index 0000000..3edc63e
--- /dev/null
+++ b/src/jebl/evolution/coalescent/IntervalList.java
@@ -0,0 +1,127 @@
+/*
+ * IntervalList.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * An interface for a set of coalescent intevals.
+ *
+ * @version $Id: IntervalList.java 305 2006-04-26 00:22:30Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ */
+public interface IntervalList {
+
+ public enum IntervalType {
+
+ /**
+ * Denotes an interval at the end of which a new sample addition is
+ * observed (i.e. the number of lineages is larger in the next interval).
+ */
+ SAMPLE("sample"),
+
+ /** Denotes an interval after which a coalescent event is observed
+ * (i.e. the number of lineages is smaller in the next interval) */
+ COALESCENT("coalescent"),
+
+ /**
+ * Denotes an interval at the end of which a migration event occurs.
+ * This means that the colour of one lineage changes.
+ */
+ MIGRATION("migration"),
+
+ /**
+ * Denotes an interval at the end of which nothing is
+ * observed (i.e. the number of lineages is the same in the next interval).
+ */
+ NOTHING("nothing");
+
+ /**
+ * private constructor.
+ */
+ private IntervalType(String name) {
+ this.name = name;
+ }
+
+ public String toString() { return name; }
+
+ private final String name;
+ }
+
+ /**
+ * get number of intervals
+ */
+ int getIntervalCount();
+
+ /**
+ * get the total number of sampling events.
+ */
+ int getSampleCount();
+
+ /**
+ * Gets an interval.
+ */
+ double getInterval(int i);
+
+ /**
+ * Returns the number of uncoalesced lineages within this interval.
+ * Required for s-coalescents, where new lineages are added as
+ * earlier samples are come across.
+ */
+ int getLineageCount(int i);
+
+ /**
+ * Returns the number coalescent events in an interval
+ */
+ int getCoalescentEvents(int i);
+
+ /**
+ * Returns the type of interval observed.
+ */
+ IntervalType getIntervalType(int i);
+
+ /**
+ * get the total duration of these intervals.
+ */
+ double getTotalDuration();
+
+ /**
+ * Checks whether this set of coalescent intervals is fully resolved
+ * (i.e. whether is has exactly one coalescent event in each
+ * subsequent interval)
+ */
+ boolean isBinaryCoalescent();
+
+ /**
+ * Checks whether this set of coalescent intervals coalescent only
+ * (i.e. whether is has exactly one or more coalescent event in each
+ * subsequent interval)
+ */
+ boolean isCoalescentOnly();
+
+
+ public class Utils {
+
+ /**
+ * @return the number of lineages at time t.
+ * @param t the time that you are counting the number of lineages
+ */
+ public static int getLineageCount(IntervalList intervals, double t) {
+
+ int i = 0;
+ while (i < intervals.getIntervalCount() && t > intervals.getInterval(i)) {
+ t -= intervals.getInterval(i);
+ i+= 1;
+ }
+ if (i == intervals.getIntervalCount()) return 1;
+ return intervals.getLineageCount(i);
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/coalescent/Intervals.java b/src/jebl/evolution/coalescent/Intervals.java
new file mode 100644
index 0000000..8ec54c5
--- /dev/null
+++ b/src/jebl/evolution/coalescent/Intervals.java
@@ -0,0 +1,228 @@
+/*
+ * Intervals.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.RootedTreeUtils;
+import jebl.evolution.graphs.Node;
+
+import java.util.Arrays;
+
+/**
+ * A concrete class for a set of coalescent intevals.
+ *
+ * @version $Id: Intervals.java 305 2006-04-26 00:22:30Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ */
+public class Intervals implements IntervalList {
+
+ public Intervals(RootedTree tree) {
+ this(tree.getNodes().size());
+
+ if (!RootedTreeUtils.isBinary(tree)) {
+ throw new IllegalArgumentException("Tree must be rooted and binary");
+ }
+ for (Node node : tree.getExternalNodes()) {
+ addSampleEvent(tree.getHeight(node));
+ }
+ for (Node node : tree.getInternalNodes()) {
+ addCoalescentEvent(tree.getHeight(node));
+ }
+ }
+
+ public Intervals(int maxEventCount) {
+ events = new Event[maxEventCount];
+ for (int i = 0; i < maxEventCount; i++) {
+ events[i] = new Event();
+ }
+ eventCount = 0;
+ sampleCount = 0;
+
+ intervals = new double[maxEventCount - 1];
+ intervalTypes = new IntervalType[maxEventCount - 1];
+ lineageCounts = new int[maxEventCount - 1];
+
+ intervalsKnown = false;
+ }
+
+ public void copyIntervals(Intervals source) {
+ intervalsKnown = source.intervalsKnown;
+ eventCount = source.eventCount;
+ sampleCount = source.sampleCount;
+
+ //don't copy the actual events..
+ /*
+ for (int i = 0; i < events.length; i++) {
+ events[i].time = source.events[i].time;
+ events[i].type = source.events[i].type;
+ }*/
+
+ if (intervalsKnown) {
+ System.arraycopy(source.intervals, 0, intervals, 0, intervals.length);
+ System.arraycopy(source.intervalTypes, 0, intervalTypes, 0, intervals.length);
+ System.arraycopy(source.lineageCounts, 0, lineageCounts, 0, intervals.length);
+ }
+ }
+
+ public void resetEvents() {
+ intervalsKnown = false;
+ eventCount = 0;
+ sampleCount = 0;
+ }
+
+ public void addSampleEvent(double time) {
+ events[eventCount].time = time;
+ events[eventCount].type = IntervalType.SAMPLE;
+ eventCount++;
+ sampleCount++;
+ intervalsKnown = false;
+ }
+
+ public void addCoalescentEvent(double time) {
+ events[eventCount].time = time;
+ events[eventCount].type = IntervalType.COALESCENT;
+ eventCount++;
+ intervalsKnown = false;
+ }
+
+ public void addMigrationEvent(double time, int destination) {
+ events[eventCount].time = time;
+ events[eventCount].type = IntervalType.MIGRATION;
+ events[eventCount].info = destination;
+ eventCount++;
+ intervalsKnown = false;
+ }
+
+ public void addNothingEvent(double time) {
+ events[eventCount].time = time;
+ events[eventCount].type = IntervalType.NOTHING;
+ eventCount++;
+ intervalsKnown = false;
+ }
+
+ public int getSampleCount() {
+ return sampleCount;
+ }
+
+ public int getIntervalCount() {
+ if (!intervalsKnown) calculateIntervals();
+ return intervalCount;
+ }
+
+ public double getInterval(int i) {
+ if (!intervalsKnown) calculateIntervals();
+ return intervals[i];
+ }
+
+ public int getLineageCount(int i) {
+ if (!intervalsKnown) calculateIntervals();
+ return lineageCounts[i];
+ }
+
+ public int getCoalescentEvents(int i) {
+ if (!intervalsKnown) calculateIntervals();
+ if (i < intervalCount-1) {
+ return lineageCounts[i]-lineageCounts[i+1];
+ } else {
+ return lineageCounts[i]-1;
+ }
+ }
+
+ public IntervalType getIntervalType(int i)
+ {
+ if (!intervalsKnown) calculateIntervals();
+ return intervalTypes[i];
+ }
+
+ public double getTotalDuration() {
+
+ if (!intervalsKnown) calculateIntervals();
+ return events[eventCount - 1].time;
+ }
+
+ public boolean isBinaryCoalescent() { return true; }
+ public boolean isCoalescentOnly() { return true; }
+
+ private void calculateIntervals() {
+
+ if (eventCount < 2) {
+ throw new IllegalArgumentException("Too few events to construct intervals");
+ }
+
+ Arrays.sort(events, 0, eventCount - 1);
+
+ if (events[0].type != IntervalType.SAMPLE) {
+ throw new IllegalArgumentException("First event is not a sample event");
+ }
+
+ intervalCount = eventCount - 1;
+
+ double lastTime = events[0].time;
+
+ int lineages = 1;
+ for (int i = 1; i < eventCount; i++) {
+
+ intervals[i - 1] = events[i].time - lastTime;
+ intervalTypes[i - 1] = events[i].type;
+ lineageCounts[i - 1] = lineages;
+ if (events[i].type == IntervalType.SAMPLE) {
+ lineages++;
+ } else if (events[i].type == IntervalType.COALESCENT) {
+ lineages--;
+ }
+ lastTime = events[i].time;
+ }
+ intervalsKnown = true;
+ }
+
+ private class Event implements Comparable {
+
+ public int compareTo(Object o) {
+ double t = ((Event)o).time;
+ if (t < time) {
+ return 1;
+ } else if (t > time) {
+ return -1;
+ } else {
+ // events are at exact same time so sort by type
+ return type.compareTo(((Event)o).type);
+ }
+ }
+
+ /**
+ * The type of event
+ */
+ IntervalType type;
+
+ /**
+ * The time of the event
+ */
+ double time;
+
+ /**
+ * Some extra information for the event (e.g., destination of a migration)
+ */
+ int info;
+
+ }
+
+ private Event[] events;
+ private int eventCount;
+ private int sampleCount;
+
+ private boolean intervalsKnown = false;
+ private double[] intervals;
+ private int[] lineageCounts;
+ private IntervalType[] intervalTypes;
+ //private int[] destinations;
+ private int intervalCount = 0;
+};
\ No newline at end of file
diff --git a/src/jebl/evolution/coalescent/LogisticGrowth.java b/src/jebl/evolution/coalescent/LogisticGrowth.java
new file mode 100644
index 0000000..f31ea22
--- /dev/null
+++ b/src/jebl/evolution/coalescent/LogisticGrowth.java
@@ -0,0 +1,133 @@
+/*
+ * LogisticGrowth.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.coalescent;
+
+/**
+ * This class models logistic growth.
+ *
+ * @version $Id: LogisticGrowth.java 390 2006-07-20 14:33:51Z rambaut $
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ */
+public class LogisticGrowth extends ExponentialGrowth {
+
+ /**
+ * Construct demographic model with default settings
+ */
+ public LogisticGrowth() {
+ // empty constructor
+ }
+
+ /**
+ * Construct demographic model with given settings
+ */
+ public LogisticGrowth(double N0, double r, double c) {
+
+ super(N0, r);
+ this.c = c;
+ }
+
+ public void setShape(double value) { c = value; }
+ public double getShape() { return c; }
+
+ /**
+ * An alternative parameterization of this model. This
+ * function sets the time at which there is a 0.5 proportion
+ * of N0.
+ */
+ public void setTime50(double time50) {
+
+ c = 1.0 / (Math.exp(getGrowthRate() * time50) - 2.0);
+
+ // The general form for any k where t50 is the time at which Nt = N0/k:
+ // c = (k - 1.0) / (Math.exp(getGrowthRate() * time50) - k);
+ }
+
+ // Implementation of abstract methods
+
+ /**
+ * Gets the value of the demographic function N(t) at time t.
+ * @param t the time
+ * @return the value of the demographic function N(t) at time t.
+ */
+ public double getDemographic(double t) {
+
+ double nZero = getN0();
+ double r = getGrowthRate();
+ double c = getShape();
+
+// return nZero * (1 + c) / (1 + (c * Math.exp(r*t)));
+// AER rearranging this to use exp(-rt) may help
+// with some overflow situations...
+
+ double common = Math.exp(-r*t);
+ return (nZero * (1 + c) * common) / (c + common);
+ }
+
+ /**
+ * Returns value of demographic intensity function at time t
+ * (= integral 1/N(x) dx from 0 to t).
+ */
+ public double getIntensity(double t) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns value of demographic intensity function at time t
+ * (= integral 1/N(x) dx from 0 to t).
+ */
+ public double getInverseIntensity(double x) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasIntegral() {
+ return true;
+ }
+
+ public double getIntegral(double start, double finish) {
+
+ double intervalLength = finish - start;
+
+ double nZero = getN0();
+ double r = getGrowthRate();
+ double c = getShape();
+ double expOfMinusRT = Math.exp(-r*start);
+ double expOfMinusRG = Math.exp(-r*intervalLength);
+
+ double term1 = nZero*(1.0+c);
+ assert(term1 > 0.0);
+
+ double term2 = c*(1.0 - expOfMinusRG);
+
+ double term3 = (term1*expOfMinusRT) * r * expOfMinusRG;
+
+ assert(term2 > 0.0 || term3 > 0.0);
+
+ double term4;
+ if (term3!=0.0 && term2==0.0) {
+ term4=0.0;
+ } else if (term3==0.0 && term2==0.0) {
+ throw new RuntimeException("term3 and term2 are both zeros. N0=" + getN0() + " growthRate=" + getGrowthRate() + "c=" + c);
+ } else {
+ term4 = term2 / term3;
+ }
+
+ double term5 = intervalLength / term1;
+
+ return term5 + term4;
+ }
+
+ //
+ // private stuff
+ //
+
+ private double c;
+}
diff --git a/src/jebl/evolution/distances/BasicDistanceMatrix.java b/src/jebl/evolution/distances/BasicDistanceMatrix.java
new file mode 100644
index 0000000..6fa5fb7
--- /dev/null
+++ b/src/jebl/evolution/distances/BasicDistanceMatrix.java
@@ -0,0 +1,124 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: BasicDistanceMatrix.java 186 2006-01-24 00:41:22Z pepster $
+ */
+public class BasicDistanceMatrix implements DistanceMatrix {
+
+ public BasicDistanceMatrix(Collection<Taxon> taxa, double[][] distances) {
+
+ if (distances == null || distances.length == 0) {
+ throw new IllegalArgumentException("Source distance matrix is null or empty");
+ }
+
+ if (distances.length != distances[0].length) {
+ throw new IllegalArgumentException("Source distance matrix is not square");
+ }
+
+ if (distances.length != taxa.size()) {
+ throw new IllegalArgumentException("Source distance matrix dimensions do not match the number of taxa");
+ }
+
+ this.taxa = new ArrayList<Taxon>(taxa);
+ this.distances = distances;
+ }
+
+ /**
+ * Gets the size of the matrix (which is square), i.e., number of rows or columns.
+ *
+ * @return the size
+ */
+ public int getSize() {
+ return distances.length;
+ }
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ public List<Taxon> getTaxa() {
+ return taxa;
+ }
+
+ /**
+ * Gets the distance at a particular row and column
+ *
+ * @param row the row index
+ * @param column the column index
+ * @return the distance
+ */
+ public double getDistance(int row, int column) {
+ return distances[row][column];
+ }
+
+ /**
+ * Gets the distance between 2 taxa
+ *
+ * @param taxonRow
+ * @param taxonColumn
+ * @return the distance
+ */
+ public double getDistance(Taxon taxonRow, Taxon taxonColumn) {
+ int row = taxa.indexOf(taxonRow);
+ if (row == -1) {
+ throw new IllegalArgumentException("The row taxon, " + taxonRow.getName() + " is not found in this matrix");
+ }
+
+ int column = taxa.indexOf(taxonColumn);
+ if (column == -1) {
+ throw new IllegalArgumentException("The column taxon, " + taxonColumn.getName() + " is not found in this matrix");
+ }
+
+ return getDistance(row, column);
+ }
+
+ /**
+ * Gets a sub-matrix for only those taxa in the collection (all
+ * of which should be present in this matrix).
+ *
+ * @param taxonSubset
+ * @return the new submatrix
+ */
+ public DistanceMatrix getSubmatrix(Collection<Taxon> taxonSubset) {
+ double[][] newDistances = new double[taxonSubset.size()][taxonSubset.size()];
+ int i = 0;
+ for (Taxon taxonRow : taxonSubset) {
+
+ int row = taxa.indexOf(taxonRow);
+ if (row == -1) {
+ throw new IllegalArgumentException("The taxon, " + taxonRow.getName() + " is not found in this matrix");
+ }
+
+ int j = 0;
+ for (Taxon taxonColumn : taxonSubset) {
+ int column = taxa.indexOf(taxonColumn);
+ if (column == -1) {
+ throw new IllegalArgumentException("The taxon, " + taxonColumn.getName() + " is not found in this matrix");
+ }
+
+ newDistances[i][j] = getDistance(row, column);
+ }
+ i++;
+ }
+ return new BasicDistanceMatrix(taxonSubset, newDistances);
+ }
+
+ /**
+ * Gets a 2-dimensional array containing the distances
+ *
+ * @return the distances
+ */
+ public double[][] getDistances() {
+ return distances;
+ }
+
+ private final List<Taxon> taxa;
+ private final double[][] distances;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/distances/CannotBuildDistanceMatrixException.java b/src/jebl/evolution/distances/CannotBuildDistanceMatrixException.java
new file mode 100644
index 0000000..e28b4c4
--- /dev/null
+++ b/src/jebl/evolution/distances/CannotBuildDistanceMatrixException.java
@@ -0,0 +1,11 @@
+package jebl.evolution.distances;
+
+/**
+ * @author Matthew Cheung
+ * @version $Id$
+ */
+public class CannotBuildDistanceMatrixException extends IllegalArgumentException {
+ public CannotBuildDistanceMatrixException(String msg){
+ super(msg);
+ }
+}
diff --git a/src/jebl/evolution/distances/DistanceMatrix.java b/src/jebl/evolution/distances/DistanceMatrix.java
new file mode 100644
index 0000000..718075b
--- /dev/null
+++ b/src/jebl/evolution/distances/DistanceMatrix.java
@@ -0,0 +1,55 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: DistanceMatrix.java 186 2006-01-24 00:41:22Z pepster $
+ */
+public interface DistanceMatrix {
+
+ /**
+ * Gets the size of the matrix (which is square), i.e., number of rows or columns.
+ * @return the size
+ */
+ int getSize();
+
+ /**
+ * @return the list of taxa that the state values correspond to.
+ */
+ List<Taxon> getTaxa();
+
+ /**
+ * Gets the distance at a particular row and column
+ * @param row the row index
+ * @param column the column index
+ * @return the distance
+ */
+ double getDistance(int row, int column);
+
+ /**
+ * Gets the distance between 2 taxa
+ * @param taxonRow
+ * @param taxonColumn
+ * @return the distance
+ */
+ double getDistance(Taxon taxonRow, Taxon taxonColumn);
+
+ /**
+ * Gets a sub-matrix for only those taxa in the collection (all
+ * of which should be present in this matrix).
+ * @param taxa
+ * @return the new submatrix
+ */
+ DistanceMatrix getSubmatrix(Collection<Taxon> taxa);
+
+ /**
+ * Gets a 2-dimensional array containing the distances
+ * @return the distances
+ */
+ double[][] getDistances();
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/distances/F84DistanceMatrix.java b/src/jebl/evolution/distances/F84DistanceMatrix.java
new file mode 100644
index 0000000..0aff1f9
--- /dev/null
+++ b/src/jebl/evolution/distances/F84DistanceMatrix.java
@@ -0,0 +1,93 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.Pattern;
+import jebl.evolution.sequences.Nucleotides;
+import jebl.evolution.sequences.State;
+import jebl.util.ProgressListener;
+
+/**
+
+ * @author Joseph Heled
+ * @version $Id: F84DistanceMatrix.java 658 2007-03-20 03:27:20Z twobeers $
+ *
+ * See the detailed comment in {@link HKYDistanceMatrix} on the model and the formula used for estimating the distance.
+ */
+
+
+public class F84DistanceMatrix extends BasicDistanceMatrix {
+
+ public F84DistanceMatrix(Alignment alignment, ProgressListener progress) {
+ super(alignment.getTaxa(), Initialaizer.getDistances(alignment, progress));
+ }
+
+ public F84DistanceMatrix(Alignment alignment) {
+ super(alignment.getTaxa(), Initialaizer.getDistances(alignment, null));
+ }
+
+ static class Initialaizer {
+
+ //
+ // Private stuff
+ //
+ private static final double MAX_DISTANCE = 1000.0;
+ private static Alignment alignment;
+
+ /**
+ * Calculate a pairwise distance
+ */
+ static private double calculatePairwiseDistance(int taxon1, int taxon2) {
+
+ double[] total = new double [4];
+ double[] transversions = new double [4];
+
+ for( Pattern pattern : alignment.getPatterns() ) {
+ State state1 = pattern.getState(taxon1);
+ State state2 = pattern.getState(taxon2);
+
+ double weight = pattern.getWeight();
+ if (!state1.isAmbiguous() && !state2.isAmbiguous() ) {
+ total[state1.getIndex()] += weight;
+
+ if( Nucleotides.isTransversion(state1, state2) ) {
+ transversions[state1.getIndex()] += weight;
+ }
+ }
+ }
+
+ double totalTransversions = 0.0;
+ for(int i = 0; i < 4; ++i) {
+ if( total[i] > 0 ) {
+ totalTransversions += transversions[i]/total[i];
+ }
+ }
+ double expDist = 1.0 - (totalTransversions / 2.0);
+ return expDist > 0 ? -Math.log( expDist) : MAX_DISTANCE;
+ }
+
+
+ synchronized static double[][] getDistances(Alignment alignment, ProgressListener progress) {
+ Initialaizer.alignment = alignment;
+
+ final int stateCount = alignment.getSequenceType().getCanonicalStateCount();
+
+ if (stateCount != 4) {
+ throw new IllegalArgumentException("F84DistanceMatrix must have nucleotide patterns");
+ }
+
+ int dimension = alignment.getTaxa().size();
+ double[][] distances = new double[dimension][dimension];
+ float tot = (dimension * (dimension - 1)) / 2;
+ int done = 0;
+ for(int i = 0; i < dimension; ++i) {
+ for(int j = i+1; j < dimension; ++j) {
+ distances[i][j] = calculatePairwiseDistance(i, j);
+ distances[j][i] = distances[i][j];
+ if( progress != null ) progress.setProgress( ++done / tot);
+ }
+ }
+
+ return distances;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/distances/HKYDistanceMatrix.java b/src/jebl/evolution/distances/HKYDistanceMatrix.java
new file mode 100644
index 0000000..9fa947b
--- /dev/null
+++ b/src/jebl/evolution/distances/HKYDistanceMatrix.java
@@ -0,0 +1,209 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.Pattern;
+import jebl.evolution.sequences.Nucleotides;
+import jebl.evolution.sequences.State;
+import jebl.util.ProgressListener;
+
+/**
+ * Compute HKY corrected distance matrix
+ *
+ * @author Andrew Rambaut
+ * @author Joseph Heled
+ * @version $Id: HKYDistanceMatrix.java 715 2007-05-24 23:13:04Z matthew_cheung $
+ *
+ * Adapted from BEAST source code.
+ *
+ * The code in this file appeared originally in the file F84DistanceMatrix, and the comment said (as above) HKY.
+ * Initially I thought it was one model under two different names, but that was my ignorance. While similar,
+ * there is an small difference which I was able to understance only by examining the transition
+ * probability and rate matrices for both models.
+ *
+ * Simply put, HKY assumes *the same* ratio of transitions/transversions for all bases, while F84 has a
+ * different ratio for Purines and Pyrimidines. The confusion stems from the fact that those two different ratios
+ * depend on just one parameter. If Kappa is the ratio for HKY then for F84
+ * Kappa(Purine aka A,G) = 1 + Kappa/(pi(A)+pi(G)) and Kappa(Pyrimidine aka C,T) = 1 + Kappr/(pi(C)+pi(T)).
+ *
+ * This difference simplifies the estimation of evolutionary distance and Kappa from F84, since some
+ * entries in the HKY transition matrix contain expressions involving exp(-t alpha), where alpha depend on both Kappa
+ * and wheather the element is a Purine or a Pyrimidine.
+ *
+ * pi(A,C,G,T) = stationary frequencies
+ * F84 HKY Tamura-Nei
+ * K(AG) = 1 + Kappa/(pi(A)+pi(G)) Kappa a1
+ *
+ * K(CT) = 1 + Kappa/(pi(C)+pi(T)) Kappa a2
+ *
+ * b = 1 1 beta
+ *
+ * Rate Matrix
+ *
+ * | - b K(AG) b | | pi(A) 0 0 0 |
+ * Q = | b - b K(CT) | | 0 pi(C) 0 0 |
+ * | K(AG) 1 - b | | 0 0 pi(G) 0 |
+ * | b K(CT) b - | | 0 0 0 pi(T) |
+ *
+ *
+ * Transition Probability Matrix
+ *
+ * PI(A) = PI(G) = pi(A) + pi(G)
+ * PI(C) = PI(T) = pi(C) + pi(T)
+ *
+ * Alpha(j) = 1 + Kappa F84
+ * 1 + PI(j)*(k-1) HKY
+ *
+ * P(transversion) = pi,(i+1)%4 = pi,(i+3)%4 = pi(i) * (1 - exp(-t))
+ * P(transition) = pi,(i+2)%4 = pi(i) + pi(j)(1/PI(j) - 1) * exp(-t) - pi(i)/PI(i) * exp(-t * Alpha(i))
+ * Pi,i = pi(i) + pi(i)(1/PI(i) - 1) * exp(-t) - (pi(i)/PI(i) - 1) * exp(-t * Alpha(i))
+ *
+ *
+ * From the above it is easy to see that for F84 Sum(all transversions) = 2 Sum(p(i)) (1 - exp(-t)) = 2(1-exp(-t))
+ * which gives the best estimate of t as -log(1 - Sum(all transversions)/2).
+ * When there are no transversions (say when sequences are very short or distance is very small) this estimate is 0
+ * even when sequences are not identical. I am not sure if there is an easy way to fix this since in this case
+ * Kappa is confused with t and only t*(Kappa+1) can be estimated.
+ *
+ * The code in this file estimates the "evolutionary distance", which is
+ * (2*Kappa*(pi(A)*pi(G) + pi(T)*pi(C)) + 2*(pi(A) + PI(C))*beta) * t. (*)
+ * This raises a question since the stationary frequencies are estimated from all the sequences, but
+ * transition/transversion rates are done for each pair individually. The result is that distances are not neccesarily
+ * scaled correctly for the matrix as a whole. I am still trying to figure this out.
+ *
+ * (*) The original code had a bug which counted only A<-->G substitutions while C<-->T where ignored.
+ *
+ */
+
+public class HKYDistanceMatrix extends BasicDistanceMatrix {
+
+ public HKYDistanceMatrix(Alignment alignment, ProgressListener progress) {
+ super(alignment.getTaxa(), Initializer.getDistances(alignment, progress));
+ }
+
+ static class Initializer extends ModelBasedDistanceMatrix {
+ //
+ // Private stuff
+ //
+ private static Alignment alignment;
+
+ //used in correction formula
+ static private double constA, constB, constC;
+
+ /**
+ * Calculate the distance between two of the sequences from the alignment.
+ */
+ static private double calculatePairwiseDistance(int taxon1, int taxon2) {
+ double sumTs = 0.0; // total weight of all columns that have a transition for these taxa
+ double sumTv = 0.0; // total weight of all columns that have a transversion for these taxa
+ double sumWeight = 0.0; // total weight of all columns (ignoring those with ambiguities, but
+ // including identical columns (which have neither a transition nor a transversion) )
+ boolean noGapsPairFound = false;
+
+ for( Pattern pattern : alignment.getPatterns() ) {
+ State state1 = pattern.getState(taxon1);
+ State state2 = pattern.getState(taxon2);
+
+ // ignore any ambiguous or gaps
+ if( state1.isAmbiguous() || state2.isAmbiguous() ) {
+ continue;
+ } else {
+ noGapsPairFound = true;
+ }
+
+ double weight = pattern.getWeight();
+ // acgt
+ if ( state1 != state2 ) {
+ if ( Nucleotides.isTransition(state1, state2) ) {
+ // it's a transition
+ sumTs += weight;
+ } else {
+ // it's a transversion
+ sumTv += weight;
+ }
+ }
+ sumWeight += weight; // this also includes the columns with state1 == state2
+ }
+
+ if(!noGapsPairFound)
+ throw new CannotBuildDistanceMatrixException("It is not possible to compute the HKY genetic distance " +
+ "for these sequences because at least one pair of sequences do not overlap in the alignment.");
+
+ if( sumWeight <= 0.0 ) {
+ return MAX_DISTANCE;
+ }
+
+ while( true ) {
+
+ double P = sumTs / sumWeight;
+ double Q = sumTv / sumWeight;
+
+ double a = 1.0 - (P / (2.0 * constA)) - (((constA - constB) * Q) / (2.0 * constA * constC));
+
+ if( a <= 0 ) {
+ // minimum number of sites whose removal restores consistency. see comments
+ // in TamuraNei.
+ final int adjustment = (int)(1 + (sumWeight * -a) / (1/(2.0*constA) - 1));
+ sumTs -= adjustment;
+ if( sumTs < 0) {
+ break;
+ }
+ sumWeight -= adjustment;
+ continue;
+ }
+
+ double b = 1.0 - (Q / (2.0 * constC));
+ if( b < 0 ) {
+ break;
+ }
+
+ final double distance = -(2.0 * constA * Math.log(a)) + (2.0 * (constA - constB - constC) * Math.log(b));
+
+ return Math.min(distance, MAX_DISTANCE);
+ }
+ return MAX_DISTANCE;
+ }
+
+
+ static double[][] getDistances(Alignment alignment, ProgressListener progress) {
+ Initializer.alignment = alignment;
+
+ // ASK Alexei
+ final int stateCount = alignment.getSequenceType().getCanonicalStateCount();
+
+ if (stateCount != 4) {
+ throw new IllegalArgumentException("HKYDistanceMatrix must have nucleotide patterns");
+ }
+
+ double[] freqs = getFrequenciesSafe(alignment);
+
+ // Ask Alexei (mapping 0-a etc)
+ double freqA = freqs[Nucleotides.A_STATE.getIndex()];
+ double freqC = freqs[Nucleotides.C_STATE.getIndex()];
+ double freqG = freqs[Nucleotides.G_STATE.getIndex()];
+ double freqT = freqs[Nucleotides.T_STATE.getIndex()];
+
+ constA = ((freqA * freqG) / freqR) + ((freqC * freqT) / freqY);
+ constB = (freqA * freqG) + (freqC * freqT);
+ constC = (freqR * freqY);
+
+ int dimension = alignment.getTaxa().size();
+ double[][] distances = new double[dimension][dimension];
+
+ // TT: Whys is this a float? It's always a whole number! If the purpose
+ // is to avoid integer division, we should rather cast to float or double
+ // in the division below
+ float tot = (dimension * (dimension - 1)) / 2;
+ int done = 0;
+
+ for(int i = 0; i < dimension; ++i) {
+ for(int j = i+1; j < dimension; ++j) {
+ distances[i][j] = calculatePairwiseDistance(i, j);
+ distances[j][i] = distances[i][j];
+ if( progress != null ) progress.setProgress( ++done / tot);
+ }
+ }
+
+ return distances;
+ }
+ }
+}
diff --git a/src/jebl/evolution/distances/JukesCantorDistanceMatrix.java b/src/jebl/evolution/distances/JukesCantorDistanceMatrix.java
new file mode 100644
index 0000000..ad6a2cc
--- /dev/null
+++ b/src/jebl/evolution/distances/JukesCantorDistanceMatrix.java
@@ -0,0 +1,111 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.Pattern;
+import jebl.evolution.sequences.State;
+import jebl.util.ProgressListener;
+
+/**
+ * Compute jukes-cantor corrected distance matrix for a set of aligned sequences.
+ * Adapted from BEAST code by joseph.
+ *
+ * @author Andrew Rambaut
+ * @author Korbinian Strimmer
+ * @author Joseph Heled
+ *
+ * @version $Id: JukesCantorDistanceMatrix.java 715 2007-05-24 23:13:04Z matthew_cheung $
+ */
+
+public class JukesCantorDistanceMatrix extends BasicDistanceMatrix {
+
+ public JukesCantorDistanceMatrix(Alignment alignment, ProgressListener progress) {
+ super(alignment.getTaxa(), getDistances(alignment, progress));
+ }
+
+ // Helpers during construction
+ private static double maxTheoreticalSubsRate;
+ private static Alignment alignment;
+ private static final double MAX_DISTANCE = 1000.0;
+
+ /**
+ * Calculate number of substitution between sequences as a ratio.
+ */
+ static private double anySubstitutionRatio(int taxon1, int taxon2) {
+ double distance;
+ double sumDistance = 0.0;
+ double sumWeight = 0.0;
+ boolean noGapsPairFound = false;
+
+ // If both sequences are of zero length then the substitution ratio is zero because they are identical
+ if(alignment.getPatterns().size() == 0)
+ return 0.0;
+
+ for( Pattern pattern : alignment.getPatterns() ) {
+ State state1 = pattern.getState(taxon1);
+ State state2 = pattern.getState(taxon2);
+
+ final double weight = pattern.getWeight();
+
+ if(!state1.isGap() && !state2.isGap())
+ noGapsPairFound = true;
+
+ if (!state1.isAmbiguous() && !state2.isAmbiguous()) {
+ if(state1 != state2)
+ sumDistance += weight;
+ }
+ sumWeight += weight;
+ }
+
+ if(!noGapsPairFound)
+ throw new CannotBuildDistanceMatrixException("It is not possible to compute the Jukes-Cantor genetic distance " +
+ "for these sequences because at least one pair of sequences do not overlap in the alignment.");
+
+ distance = sumDistance / sumWeight;
+
+ return distance;
+ }
+
+
+ /**
+ * Calculate a pairwise distance between the i'th and j'th taxons/sequences
+ */
+ static private double calculatePairwiseDistance(int taxon1, int taxon2) {
+ double obsDist = anySubstitutionRatio(taxon1, taxon2);
+
+ if (obsDist == 0.0) return 0.0;
+
+ // protect against log(negative number)
+ if (obsDist >= maxTheoreticalSubsRate) {
+ return MAX_DISTANCE;
+ }
+
+ double expDist = -maxTheoreticalSubsRate * Math.log(1.0 - ((1/maxTheoreticalSubsRate) * obsDist));
+
+ return Math.min(expDist, MAX_DISTANCE);
+ }
+
+ synchronized static double[][] getDistances(Alignment alignment, ProgressListener progress) {
+ JukesCantorDistanceMatrix.alignment = alignment;
+
+ // ASK Alexei
+ int stateCount = alignment.getSequenceType().getCanonicalStateCount();
+
+ maxTheoreticalSubsRate = ((double)stateCount - 1) / stateCount;
+
+ int dimension = alignment.getTaxa().size();
+ double[][] distances = new double[dimension][dimension];
+
+ float tot = (dimension * (dimension - 1)) / 2;
+ int done = 0;
+ for(int i = 0; i < dimension; ++i) {
+ for(int j = i+1; j < dimension; ++j) {
+ distances[i][j] = calculatePairwiseDistance(i, j);
+ distances[j][i] = distances[i][j];
+
+ if( progress != null ) progress.setProgress( ++done / tot);
+ }
+ }
+
+ return distances;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/distances/ModelBasedDistanceMatrix.java b/src/jebl/evolution/distances/ModelBasedDistanceMatrix.java
new file mode 100644
index 0000000..1b446c0
--- /dev/null
+++ b/src/jebl/evolution/distances/ModelBasedDistanceMatrix.java
@@ -0,0 +1,130 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.Nucleotides;
+
+import java.util.List;
+
+/**
+ *
+ * @author Joseph Heled
+ * @version $Id: ModelBasedDistanceMatrix.java 389 2006-07-18 06:50:27Z twobeers $
+ *
+ */
+public class ModelBasedDistanceMatrix {
+ protected static final double MAX_DISTANCE = 1000.0;
+
+ protected static double freqR, freqY;
+
+ /**
+ * @param sequences
+ * @return array holding the count of each canonical state in the sequences.
+ * E.g. for nucleotide sequences, this array will have length 4.
+ */
+ private static int[] countStates(List<Sequence> sequences) {
+ if (sequences.isEmpty()) {
+ throw new IllegalArgumentException("No sequences passed in - unable to determine sequence type");
+ }
+ SequenceType sequenceType = sequences.get(0).getSequenceType();
+ final int canonicalStateCount = sequenceType.getCanonicalStateCount();
+ int[] counts = new int[canonicalStateCount];
+ for( Sequence sequence : sequences ) {
+ if (!sequence.getSequenceType().equals(sequenceType)) {
+ throw new IllegalArgumentException("Sequences of mixed type");
+ }
+ for( int i : sequence.getStateIndices() ) {
+ // ignore non definite states (ask alexei)
+ if( i < canonicalStateCount ) {
+ ++counts[i];
+ }
+ }
+ }
+ return counts;
+ }
+
+ /**
+ * Same as countStates, but if any of the counts would normally be 0, this
+ * method adds 1 to each count to avoid counts of 0.
+ *
+ * @param sequences
+ * @return approximation of state counts, each guaranteed to be > 0.
+ */
+ private static int[] countStatesSafe(List<Sequence> sequences) {
+ int[] counts = countStates(sequences);
+ int numSequences = counts.length;
+
+ boolean anyZero = false;
+
+ for (int i=0; i < numSequences; i++) {
+ anyZero |= (counts[i] == 0);
+ }
+
+ // if any of the counts are 0, adjust all of them by 1 to avoid
+ // division by 0 in extreme cases
+ if (anyZero) {
+ for (int i = 0; i < numSequences; ++i) {
+ counts[i]++;
+ }
+ }
+ return counts;
+ }
+
+ private static double[] getFrequenciesMaybeSafe(List<Sequence> sequences, boolean safe) {
+ SequenceType sequenceType = sequences.get(0).getSequenceType();
+ int[] counts = (safe ? countStatesSafe(sequences) : countStates(sequences));
+ int canonicalStateCount = counts.length;
+ double[] freqs = new double[canonicalStateCount];
+
+ // calculate total number of residues
+ long count = 0;
+ for (int i=0; i < canonicalStateCount; i++) {
+ count += counts[i];
+ }
+ for (int i=0; i < canonicalStateCount; i++) {
+ freqs[i] = (double) counts[i] / (double) count;
+ }
+
+ if (sequenceType.equals(SequenceType.NUCLEOTIDE)) {
+ freqR = freqs[Nucleotides.A_STATE.getIndex()] + freqs[Nucleotides.G_STATE.getIndex()];
+ freqY = freqs[Nucleotides.C_STATE.getIndex()] + freqs[Nucleotides.T_STATE.getIndex()];
+ }
+ return freqs;
+ }
+
+
+ /**
+ *
+ * As a side effect, this method sets freqR and freqY if called on
+ * nucleotide sequences.
+ *
+ * @param sequences A list of sequences of the same type
+ * @return Approximation of the relative canonical state
+ * frequencies in the sequences; Each frequency
+ * is guaranteed to be > 0 (and therefore it can
+ * only be an approximation).
+ */
+ protected static double[] getFrequenciesSafe(List<Sequence> sequences) {
+ return getFrequenciesMaybeSafe(sequences, true);
+ }
+
+ protected static double[] getFrequenciesSafe(Alignment alignment) {
+ return getFrequenciesSafe(alignment.getSequenceList());
+ }
+
+ /**
+ * As a side effect, this method sets freqR and freqY if called on
+ * nucleotide sequences.
+ *
+ * @param sequences A list of sequences of the same type
+ * @return Relative canonical state frequencies in the sequences;
+ */
+ protected static double[] getFrequencies(List<Sequence> sequences) {
+ return getFrequenciesMaybeSafe(sequences, false);
+ }
+
+ protected static double[] getFrequencies(Alignment alignment) {
+ return getFrequencies(alignment.getSequenceList());
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/distances/TamuraNeiDistanceMatrix.java b/src/jebl/evolution/distances/TamuraNeiDistanceMatrix.java
new file mode 100644
index 0000000..dd512ff
--- /dev/null
+++ b/src/jebl/evolution/distances/TamuraNeiDistanceMatrix.java
@@ -0,0 +1,172 @@
+package jebl.evolution.distances;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.Pattern;
+import jebl.evolution.sequences.Nucleotides;
+import jebl.evolution.sequences.State;
+import jebl.util.ProgressListener;
+
+/**
+ * Date: 22/01/2006
+ * Time: 17:28:59
+ *
+ * @author Joseph Heled
+ * @version $Id: TamuraNeiDistanceMatrix.java 715 2007-05-24 23:13:04Z matthew_cheung $
+ *
+ * Estimation of the Number of Nucleotide Substitutions in
+ * the Control Region of Mitochondrial DNA in Humans and
+ * Chimpanzees. Koichiro Tamura and Masatoshi Nei, 1993
+ *
+ * Estimated Distance is d = 2 (pi(A) pi(G) k_R + pi(T) pi(C) k_Y + PI(A)PI(C)) t, where k_R/k_Y are the rates of
+ * Purine/Pyrimidine respectivly.
+ *
+ * When distances grow large, the formulas break as estimates of number of transition/transversion becomes inconsistent,
+ * which results in negative logs. Returning "infinity" is not optimal as it breaks operations such as constructing
+ * consensus trees where distances for resampled sequences vary between (say) 2-3 and out "infinity" 10.
+ * As a workaround I try to reduce the number of transitions/transversions by the minimum amout which brings the estimates
+ * back to a consistent state.
+ */
+
+public class TamuraNeiDistanceMatrix extends BasicDistanceMatrix {
+
+ public TamuraNeiDistanceMatrix(Alignment alignment, ProgressListener progress) {
+ super(alignment.getTaxa(), Initializer.getDistances(alignment, progress));
+ }
+
+ static class Initializer extends ModelBasedDistanceMatrix {
+
+ private static Alignment alignment;
+
+ // used in correction formula
+ private static double constA1, constA2, constC;
+
+ /**
+ * Calculate a pairwise distance
+ */
+ static private double calculatePairwiseDistance(int taxon1, int taxon2) {
+
+ double sumTsAG = 0.0;
+ double sumTsCT = 0.0;
+ double sumTv = 0.0;
+ double sumWeight = 0.0;
+ boolean noGapsPairFound = false;
+
+ for( Pattern pattern : alignment.getPatterns() ) {
+ State state1 = pattern.getState(taxon1);
+ State state2 = pattern.getState(taxon2);
+
+ double weight = pattern.getWeight();
+ // acgt
+
+ // ignore any ambiguous states or gaps
+ if( state1.isAmbiguous() || state2.isAmbiguous() ) {
+ continue;
+ } else {
+ noGapsPairFound = true;
+ }
+
+
+ if ( state1 != state2 ) {
+ if ( Nucleotides.isTransition(state1, state2) ) {
+ // it's a transition
+ if( Nucleotides.isPurine(state1) ) {
+ sumTsAG += weight;
+ } else {
+ sumTsCT += weight;
+ }
+ } else {
+ // it's a transversion
+ sumTv += weight;
+ }
+ }
+ sumWeight += weight;
+ }
+
+ if(! noGapsPairFound ) {
+ throw new CannotBuildDistanceMatrixException("It is not possible to compute the Tamura-Nei genetic distance " +
+ "for these sequences because at least one pair of sequences do not overlap in the alignment.");
+ }
+
+ // Unfortuanetly adjusting number of sites for Purine/Pyrimidine may turn the other into negative - so
+ // we iterate untile both estimates are consistent
+ while( true ) {
+
+ double P1 = sumTsAG / sumWeight;
+ double P2 = sumTsCT / sumWeight;
+ double Q = sumTv / sumWeight;
+
+ double a1 = 1.0 - P1 * (1 / (2 * constA1)) - Q * (1 / (2 * freqR));
+ double a2 = 1.0 - P2 * (1 / (2 * constA2)) - Q * (1 / (2 * freqY));
+
+ if( a1 <= 0 ) {
+ // smallest number of sites to remove which makes a1 positive.
+ int adjustment = (int)(1 + (sumWeight * -a1) / ((1 / (2 * constA1)) - 1));
+ sumTsAG -= adjustment;
+ if( sumTsAG < 0 ) break;
+ sumWeight -= adjustment;
+ continue;
+ }
+
+ if( a2 <= 0 ) {
+ // smallest number of sites to remove which makes a2 positive.
+ int adjustment = (int)(1 + (sumWeight * -a2) / ((1 / (2 * constA2)) - 1));
+ sumTsCT -= adjustment;
+ if( sumTsCT < 0 ) break;
+ sumWeight -= adjustment;
+ continue;
+ }
+
+ double b = 1.0 - (Q / (2.0 * constC));
+ if( b <= 0 ) {
+ break;
+ }
+
+ double distance = -2.0 * ((constC - constA1*freqY - constA2*freqR) * Math.log(b)
+ + constA1 * Math.log(a1) + constA2 * Math.log(a2));
+
+ return Math.min(distance, MAX_DISTANCE);
+ }
+ return MAX_DISTANCE;
+ }
+
+
+ static double[][] getDistances(Alignment alignment, ProgressListener progress) {
+ Initializer.alignment = alignment;
+
+ // ASK Alexei
+ final int stateCount = alignment.getSequenceType().getCanonicalStateCount();
+
+ if (stateCount != 4) {
+ throw new IllegalArgumentException("Tamura NeiDistanceMatrix must have nucleotide patterns");
+ }
+
+ double[] freqs = getFrequenciesSafe(alignment);
+
+ double freqA = freqs[Nucleotides.A_STATE.getIndex()];
+ double freqC = freqs[Nucleotides.C_STATE.getIndex()];
+ double freqG = freqs[Nucleotides.G_STATE.getIndex()];
+ double freqT = freqs[Nucleotides.T_STATE.getIndex()];
+
+ // avoid arithmetic underflow by dividing first
+ constA1 = freqA * (freqG / freqR);
+ constA2 = freqT * (freqC / freqY);
+ constC = (freqR * freqY);
+
+ assert(constA1 > 0.0 && constA2 > 0.0 && constC > 0.0);
+
+ final int dimension = alignment.getTaxa().size();
+ double[][] distances = new double[dimension][dimension];
+
+ float tot = (dimension * (dimension - 1)) / 2;
+ int done = 0;
+ for(int i = 0; i < dimension; ++i) {
+ for(int j = i+1; j < dimension; ++j) {
+ distances[i][j] = calculatePairwiseDistance(i, j);
+ distances[j][i] = distances[i][j];
+ if( progress != null ) progress.setProgress( ++done / tot);
+ }
+ }
+ return distances;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/graphs/Edge.java b/src/jebl/evolution/graphs/Edge.java
new file mode 100644
index 0000000..1139973
--- /dev/null
+++ b/src/jebl/evolution/graphs/Edge.java
@@ -0,0 +1,18 @@
+package jebl.evolution.graphs;
+
+import jebl.util.Attributable;
+
+/**
+ * Represents a node in a graph or tree. In general it is
+ * used only as a handle to traverse a graph or tree structure and
+ * it has no methods or instance variables.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Edge.java 295 2006-04-14 14:59:10Z rambaut $
+ */
+public interface Edge extends Attributable {
+
+ double getLength();
+}
diff --git a/src/jebl/evolution/graphs/Graph.java b/src/jebl/evolution/graphs/Graph.java
new file mode 100644
index 0000000..13fc18d
--- /dev/null
+++ b/src/jebl/evolution/graphs/Graph.java
@@ -0,0 +1,97 @@
+/*
+ * Graph.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.graphs;
+
+import jebl.util.Attributable;
+
+import java.util.Set;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Graph.java 310 2006-05-02 09:37:07Z rambaut $
+ */
+public interface Graph extends Attributable {
+
+
+ /**
+ * Returns a list of edges connected to this node
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ List<Edge> getEdges(Node node);
+
+ /**
+ * Returns a list of nodes connected to this node by an edge
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ List<Node> getAdjacencies(Node node);
+
+ /**
+ * Returns the Edge that connects these two nodes
+ * @param node1
+ * @param node2
+ * @return the edge object.
+ * @throws NoEdgeException if the nodes are not directly connected by an edge.
+ */
+ Edge getEdge(Node node1, Node node2) throws NoEdgeException;
+
+ /**
+ * Returns the length of the edge that connects these two nodes
+ * @param node1
+ * @param node2
+ * @return the edge length.
+ * @throws NoEdgeException if the nodes are not directly connected by an edge.
+ */
+ double getEdgeLength(Node node1, Node node2) throws NoEdgeException;
+
+ /**
+ * Returns an array of 2 nodes which are the nodes at either end of the edge.
+ * @param edge
+ * @return an array of 2 edges
+ */
+ Node[] getNodes(Edge edge);
+
+ /**
+ * @return the set of all nodes in this graph.
+ */
+ Set<Node> getNodes();
+
+ /**
+ * @return the set of all edges in this graph.
+ */
+ Set<Edge> getEdges();
+
+ /**
+ * @param degree the number of edges connected to a node
+ * @return a set containing all nodes in this graph of the given degree.
+ */
+ Set<Node> getNodes(int degree);
+
+ /**
+ * This class is thrown by getEdgeLength(node1, node2) if node1 and node2
+ * are not directly connected by an edge.
+ */
+ public class NoEdgeException extends Exception {}
+
+ public class Utils {
+
+ /**
+ * @param graph
+ * @param node
+ * @return the number of edges attached to this node.
+ */
+ public static int getDegree(Graph graph, Node node) {
+ return graph.getAdjacencies(node).size();
+ }
+ }
+}
diff --git a/src/jebl/evolution/graphs/Node.java b/src/jebl/evolution/graphs/Node.java
new file mode 100644
index 0000000..23f41f5
--- /dev/null
+++ b/src/jebl/evolution/graphs/Node.java
@@ -0,0 +1,26 @@
+/*
+ * Node.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.graphs;
+
+import jebl.util.Attributable;
+
+/**
+ * Represents a node in a graph or tree. In general it is
+ * used only as a handle to traverse a graph or tree structure and
+ * it has no methods or instance variables.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Node.java 295 2006-04-14 14:59:10Z rambaut $
+ */
+public interface Node extends Attributable {
+
+ int getDegree();
+}
diff --git a/src/jebl/evolution/graphs/Utils.java b/src/jebl/evolution/graphs/Utils.java
new file mode 100644
index 0000000..15f6892
--- /dev/null
+++ b/src/jebl/evolution/graphs/Utils.java
@@ -0,0 +1,45 @@
+/*
+ * Utils.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.graphs;
+
+/**
+ * A collection of utility functions for graphs.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Utils.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class Utils {
+
+ /**
+ * @param graph
+ * @return true if the given graph is acyclic.
+ */
+ public boolean isAcyclical(Graph graph) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * @param graph
+ * @return true if the given graph is fully connected.
+ */
+ public boolean isConnected(Graph graph) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * @param graph
+ * @return true if the given graph is a tree, i.e. is acyclic
+ * and fully connected.
+ */
+ public final boolean isTree(Graph graph) {
+ return isAcyclical(graph) && isConnected(graph);
+ }
+}
diff --git a/src/jebl/evolution/io/AlignmentExporter.java b/src/jebl/evolution/io/AlignmentExporter.java
new file mode 100644
index 0000000..94e28a1
--- /dev/null
+++ b/src/jebl/evolution/io/AlignmentExporter.java
@@ -0,0 +1,21 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+
+import java.io.IOException;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: AlignmentExporter.java 540 2006-11-23 19:59:23Z pepster $
+ */
+public interface AlignmentExporter {
+
+ /**
+ * export one alignment.
+ * @param alignment to export
+ * @throws java.io.IOException
+ */
+ void exportAlignment(Alignment alignment) throws IOException;
+}
diff --git a/src/jebl/evolution/io/AlignmentImporter.java b/src/jebl/evolution/io/AlignmentImporter.java
new file mode 100644
index 0000000..11e3e72
--- /dev/null
+++ b/src/jebl/evolution/io/AlignmentImporter.java
@@ -0,0 +1,20 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: AlignmentImporter.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public interface AlignmentImporter {
+
+ /**
+ * importAlignment.
+ */
+ List<Alignment> importAlignments() throws IOException, ImportException;
+}
diff --git a/src/jebl/evolution/io/ByteBuilder.java b/src/jebl/evolution/io/ByteBuilder.java
new file mode 100644
index 0000000..8025735
--- /dev/null
+++ b/src/jebl/evolution/io/ByteBuilder.java
@@ -0,0 +1,67 @@
+package jebl.evolution.io;
+
+/**
+ * Similar to a StringBuilder, but its internal buffer is a byte[] with
+ * one entry for each character, so it can only correctly append single-byte
+ * characters.
+ *
+ * @author Joseph Heled
+ * @version $Id: ByteBuilder.java 648 2007-03-12 01:27:50Z twobeers $
+ */
+public class ByteBuilder implements CharSequence {
+ int maxCapacity;
+ int current;
+ byte[] data;
+
+ void ensureCapacity(final int cap) {
+ if( cap > data.length ) {
+ int newLen = 2*(cap+1);
+ if( newLen <= 0 ) {
+ newLen += 256;
+ }
+ if( newLen > maxCapacity) newLen = maxCapacity;
+ if( newLen < cap ) newLen = cap;
+ byte[] d = new byte[newLen];
+ System.arraycopy(data, 0, d, 0, data.length);
+ data = d;
+ }
+ }
+
+ /**
+ * Constructs a ByteBuilder that will never grow beyond <code>maxCapacity</code>
+ * bytes in length.
+ * @param maxCapacity
+ */
+ public ByteBuilder(int maxCapacity) {
+ this.maxCapacity = maxCapacity;
+ current = 0;
+ data = new byte[16];
+ }
+
+
+ public ByteBuilder append(char c) {
+ if( current + 1 > data.length ) {
+ ensureCapacity(current + 1);
+ }
+ // will throw an exception if insufficient capacity (maxCapacity reached)
+ data[current] = (byte)c;
+ ++current;
+ return this;
+ }
+
+ public int length() {
+ return current;
+ }
+
+ public char charAt(int index) {
+ return (char)data[index];
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return new String(data, start, end - start);
+ }
+
+ public String toString() {
+ return new String(data, 0, current);
+ }
+}
diff --git a/src/jebl/evolution/io/DistanceMatrixImporter.java b/src/jebl/evolution/io/DistanceMatrixImporter.java
new file mode 100644
index 0000000..c1c8655
--- /dev/null
+++ b/src/jebl/evolution/io/DistanceMatrixImporter.java
@@ -0,0 +1,21 @@
+package jebl.evolution.io;
+
+import jebl.evolution.distances.DistanceMatrix;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: DistanceMatrixImporter.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public interface DistanceMatrixImporter {
+
+ enum Triangle { LOWER, UPPER, BOTH };
+
+ /**
+ * importDistances.
+ */
+ List<DistanceMatrix> importDistanceMatrices() throws IOException, ImportException;
+}
diff --git a/src/jebl/evolution/io/FastaExporter.java b/src/jebl/evolution/io/FastaExporter.java
new file mode 100644
index 0000000..9e2c3d0
--- /dev/null
+++ b/src/jebl/evolution/io/FastaExporter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2005 JEBL Development team. All Rights Reserved.
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.taxa.Taxon;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.List;
+import java.util.Collection;
+
+/**
+ * Class for exporting a fasta file format.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: FastaExporter.java 442 2006-09-05 21:59:20Z matt_kearse $
+ */
+public class FastaExporter implements SequenceExporter {
+
+ /**
+ * Constructor
+ */
+ public FastaExporter(Writer writer) {
+ this.writer = new PrintWriter(writer);
+ }
+
+ /**
+ * export alignment or set of sequences.
+ */
+ public void exportSequences(Collection<? extends Sequence> sequences) throws IOException {
+ for (Sequence sequence : sequences) {
+ final Taxon taxon = sequence.getTaxon();
+ String desc = (String) sequence.getAttribute(FastaImporter.descriptionPropertyName);
+ if(desc== null) desc = (String) taxon.getAttribute(FastaImporter.descriptionPropertyName);
+ writer.println(">" + taxon.getName().replace(' ','_') + ((desc != null) ? (" " + desc) : ""));
+ writer.println(sequence.getString());
+ }
+ }
+
+ private final PrintWriter writer;
+}
diff --git a/src/jebl/evolution/io/FastaImporter.java b/src/jebl/evolution/io/FastaImporter.java
new file mode 100644
index 0000000..469ae1a
--- /dev/null
+++ b/src/jebl/evolution/io/FastaImporter.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2005 Your Corporation. All Rights Reserved.
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.Utils;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.ProgressListener;
+
+import javax.swing.*;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Class for importing Fasta sequential file format.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Joseph Heled
+ * @version $Id: FastaImporter.java 704 2007-05-08 03:42:09Z matt_kearse $
+ */
+public class FastaImporter implements SequenceImporter, ImmediateSequenceImporter {
+
+ /**
+ * Name of Jebl sequence property which stores sequence description (i.e. anything after sequence name in fasta
+ * file), so this data is available and an export to fasta can preserves the original data.
+ * This is stored some attribute of the sequence and of the taxon for backwards compatibility.
+ * Generally, attributes on taxon should not be used, as they are unsafe
+ * when dealing with objects that share the same taxon.
+ */
+ public static final String descriptionPropertyName = "description";
+
+ private final ImportHelper helper;
+ private final SequenceType sequenceType;
+ private final boolean closeReaderAtEnd;
+
+ private final Reader reader;
+ private IllegalCharacterPolicy illegalCharacterPolicy = IllegalCharacterPolicy.abort;
+
+ /**
+ * Use this constructor if you are reading from a file. The advantage over the
+ * other constructor is that a) the input size is known, so read() can report
+ * meaningful progress, and b) the file is closed at the end.
+ *
+ * WARNING: You cannot reuse the FastaImporter thus constructed to import sequences
+ * from the same file again.
+ *
+ * @param file
+ * @param sequenceType
+ * @throws FileNotFoundException
+ */
+ public FastaImporter(File file, SequenceType sequenceType) throws FileNotFoundException {
+ this(new BufferedReader(new FileReader(file)), sequenceType, true);
+ helper.setExpectedInputLength(file.length());
+ }
+
+ public void setIllegalCharacterPolicy(IllegalCharacterPolicy newPolicy) {
+ this.illegalCharacterPolicy = newPolicy;
+ }
+
+ /**
+ * This constructor should normally never be needed because usually we
+ * want to import from a file. Then, the constructor expecting a file
+ * should be used. Therefore, this constructor is deprecated for now.
+ * @param reader holds sequences data
+ * @param sequenceType pre specified sequences type. We should try and guess them some day.
+ */
+ @Deprecated
+ public FastaImporter(Reader reader, SequenceType sequenceType) {
+ this(reader, sequenceType, false);
+ }
+
+ private FastaImporter(final Reader reader, final SequenceType sequenceType, final boolean closeReaderAtEnd) {
+ this.reader = reader;
+ this.sequenceType = sequenceType;
+ this.closeReaderAtEnd = closeReaderAtEnd;
+ helper = new ImportHelper(reader);
+ helper.setCommentDelimiters(';');
+ }
+
+ /**
+ * @param callback Optional callback to report imported sequences to.
+ * @param progressListener Listener to report progress to. Must not be null.
+ * @return null if a callback was specified; otherwise, return list of sequences from file.
+ * @throws IOException
+ * @throws ImportException
+ */
+ private List<Sequence> read(ImmediateSequenceImporter.Callback callback,
+ ProgressListener progressListener)
+ throws IOException, ImportException
+ {
+ final List<Sequence> sequences = (callback == null) ? new ArrayList<Sequence>() : null;
+ final char fastaFirstChar = '>';
+ final String fasta1stCharAsString = new String(new char[]{fastaFirstChar});
+ final SequenceType seqtypeForGapsAndMissing = sequenceType != null ? sequenceType : SequenceType.NUCLEOTIDE;
+ final AtomicReference<IllegalCharacterPolicy> illegalCharacterPolicyForThisImport
+ = new AtomicReference<IllegalCharacterPolicy>(illegalCharacterPolicy);
+ try {
+ // find fasta line start
+ while (helper.read() != fastaFirstChar) {
+ }
+
+ do {
+ final String line = helper.readLine();
+ final StringTokenizer tokenizer = new StringTokenizer(line, " \t");
+ /*
+ * edited by me
+ */
+ String name = ImportHelper.convertControlsChars(tokenizer.nextToken()).replace('-', '_');
+
+ final String description = tokenizer.hasMoreElements() ?
+ ImportHelper.convertControlsChars(tokenizer.nextToken("")) : null;
+ StringBuilder seq = new StringBuilder();
+
+
+// Runtime s_runtime = Runtime.getRuntime();
+// s_runtime.gc();
+// System.out.println("before read " + (s_runtime.totalMemory() - s_runtime.freeMemory())/1000 + " / " + s_runtime.totalMemory()/1000);
+
+ /*
+ * We pass the StringBuilder in here instead of returning a string so
+ * that we need to hold the sequence string in memory only once. So
+ * althought this code seems a little unintuitive at first, please leave
+ * it that way :)
+ */
+ helper.readSequence(seq, seqtypeForGapsAndMissing, fasta1stCharAsString, Integer.MAX_VALUE, "-", "?", "", null, progressListener);
+
+// s_runtime.gc();
+// System.out.println("after reading sequence " + (s_runtime.totalMemory() - s_runtime.freeMemory())/1000 + " / " + s_runtime.totalMemory()/1000);
+
+ if (progressListener.isCanceled()) break;
+
+ final Taxon taxon = Taxon.getTaxon(name);
+ if (description != null && description.length() > 0) {
+ taxon.setAttribute(descriptionPropertyName, description);
+ }
+
+ // fixed guessSequenceType so it does not allocate anything
+ // note: guessSequenceType can take a fair while if the sequence is long, but doesn't report
+ // progress; ImportHelper has already reported all progress for this sequence complete when it
+ // finished reading it, although really there is still some work to do...
+ SequenceType type = ( sequenceType != null ) ? sequenceType : Utils.guessSequenceType(seq);
+
+ // todo: We don't normally want to pop up dialogs in an importer, but I don't see
+ // another clean way of handling this case. Note that the dialog only appears
+ // if illegalCharacterPolicy has been set to a non-default value.
+ if( type == null ) {
+ final String errorMessage = "Illegal sequence characters encountered on or before line " + helper.getLineNumber() + ".";
+ if (illegalCharacterPolicyForThisImport.get().equals(IllegalCharacterPolicy.askUser)) {
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ IllegalCharacterPolicy[] options = {IllegalCharacterPolicy.abort, IllegalCharacterPolicy.strip};
+ int choice = JOptionPane.showOptionDialog(null,errorMessage + " What do you want to do?", "Illegal characters in sequences",
+ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
+ illegalCharacterPolicyForThisImport.set(options[choice]);
+ }
+ });
+
+ if (illegalCharacterPolicyForThisImport.equals(IllegalCharacterPolicy.abort)) {
+ // user was presented warning and chose to abort -> abort without an exception
+ return sequences;
+ }
+ } catch (InterruptedException e) {
+ illegalCharacterPolicyForThisImport.set(IllegalCharacterPolicy.abort);
+ } catch (InvocationTargetException e) {
+ illegalCharacterPolicyForThisImport.set(IllegalCharacterPolicy.abort);
+ }
+ }
+ switch (illegalCharacterPolicyForThisImport.get()) {
+ case strip:
+ removeNonAminoAcidOrNucleotideCharacters(seq);
+ type = Utils.guessSequenceType(seq);
+ if (type==null) {
+ throw new ImportException("The file contains both residues that are only nucleotides (e.g. U) and residues that are only amino acids (e.g. E). You should import the file using file type 'Fasta (nucleotide)' or 'Fasta (amino acid)' instead of 'Fasta (auto-detect)'.");
+ }
+ assert(type != null);
+ break;
+ default:
+ throw new ImportException(errorMessage);
+ }
+ }
+
+ // now we need more again
+ BasicSequence sequence = new BasicSequence(type, taxon, seq);
+
+ // get rid of memory used by the builder
+ seq.setLength(0); seq.trimToSize(); // System.gc();
+
+ if (description != null && description.length() > 0) {
+ sequence.setAttribute(descriptionPropertyName, description);
+ }
+ if( callback != null ) {
+ // this may use more memeory by getting the string from the jebl seq yet again
+ callback.add(sequence);
+ } else {
+ sequences.add(sequence);
+ }
+ // helper.getProgress currently assumes each character to be
+ // one byte long, but this should be ok for fasta files.
+ } while (!progressListener.isCanceled() && (helper.getLastDelimiter() == fastaFirstChar));
+ } catch (EOFException e) {
+ // catch end of file the ugly way.
+ } catch (NoSuchElementException e) {
+ throw new ImportException("Incorrectly formatted fasta file (near line " + helper.getLineNumber() + ")");
+ } finally {
+ if (closeReaderAtEnd && reader != null) {
+ reader.close();
+ }
+ }
+ return sequences;
+ }
+
+ /**
+ * @param sequence A nucleotide or amino acid sequence, possibly containing some illegal characters.
+ * @return sequence with all characters that are neither a valid nucleotide nor amino acid symbol
+ * replaced.
+ */
+ static void removeNonAminoAcidOrNucleotideCharacters(StringBuilder sequence) {
+ StringBuilder result = new StringBuilder();
+ int writeIndex = 0;
+ for (int readIndex = 0; readIndex < sequence.length(); readIndex++) {
+ char c = sequence.charAt(readIndex);
+ if (SequenceType.AMINO_ACID.getState(c)!= null || SequenceType.NUCLEOTIDE.getState(c) != null) {
+ sequence.setCharAt(writeIndex, c);
+ writeIndex++;
+ }
+ }
+ sequence.setLength(writeIndex);
+ }
+
+
+ /**
+ * @return sequences from file.
+ * @throws IOException
+ * @throws ImportException
+ */
+ public final List<Sequence> importSequences() throws IOException, ImportException {
+ return read(null, ProgressListener.EMPTY);
+ }
+
+ public void importSequences(Callback callback, ProgressListener progressListener) throws IOException, ImportException {
+ read(callback, progressListener);
+ }
+}
diff --git a/src/jebl/evolution/io/IllegalCharacterPolicy.java b/src/jebl/evolution/io/IllegalCharacterPolicy.java
new file mode 100644
index 0000000..814ac77
--- /dev/null
+++ b/src/jebl/evolution/io/IllegalCharacterPolicy.java
@@ -0,0 +1,33 @@
+package jebl.evolution.io;
+
+/**
+ * What to do when an imported document contains illegal characters
+ *
+ * @author Tobias Thierer
+ * @version $Id: IllegalCharacterPolicy.java 551 2006-12-04 03:05:01Z twobeers $
+ * <p/>
+ * created on 04.12.2006 15:15:54
+ */
+public enum IllegalCharacterPolicy {
+ abort("Abort"),
+ strip("Strip offending characters"),
+ askUser("Ask");
+
+ public final String description;
+ IllegalCharacterPolicy(String description) {
+ this.description = description;
+ }
+
+ public static IllegalCharacterPolicy instanceOf(String description) {
+ for (IllegalCharacterPolicy p : values()) {
+ if (p.description.equals(description)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ public String toString() {
+ return description;
+ }
+}
diff --git a/src/jebl/evolution/io/ImmediateSequenceImporter.java b/src/jebl/evolution/io/ImmediateSequenceImporter.java
new file mode 100644
index 0000000..65b2c68
--- /dev/null
+++ b/src/jebl/evolution/io/ImmediateSequenceImporter.java
@@ -0,0 +1,23 @@
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.Sequence;
+import jebl.util.ProgressListener;
+
+import java.io.IOException;
+
+/**
+ *
+ * A sequence importer sending the sequences back one by one, which makes it
+ * possible to import larger documents if handled wisely on the other side.
+ *
+ * @author Joseph Heled
+ * @version $Id: ImmediateSequenceImporter.java 465 2006-10-04 04:24:20Z twobeers $
+ *
+ */
+public interface ImmediateSequenceImporter {
+ public interface Callback {
+ void add(Sequence seq);
+ }
+
+ void importSequences(Callback callback, ProgressListener progressListener) throws IOException, ImportException;
+}
diff --git a/src/jebl/evolution/io/ImportException.java b/src/jebl/evolution/io/ImportException.java
new file mode 100644
index 0000000..c08ec76
--- /dev/null
+++ b/src/jebl/evolution/io/ImportException.java
@@ -0,0 +1,58 @@
+package jebl.evolution.io;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: ImportException.java 609 2007-01-08 00:40:49Z pepster $
+ */
+public class ImportException extends Exception {
+ public ImportException() { super(); }
+ public ImportException(String message) { super(message); }
+ public String userMessage() { return getMessage(); }
+
+ public static class DuplicateFieldException extends ImportException {
+ public DuplicateFieldException() { super(); }
+ public DuplicateFieldException(String message) { super(message); }
+ }
+
+ public static class BadFormatException extends ImportException {
+ public BadFormatException() { super(); }
+ public BadFormatException(String message) { super(message); }
+ }
+
+ public static class UnparsableDataException extends ImportException {
+ public UnparsableDataException() { super(); }
+ public UnparsableDataException(String message) { super(message); }
+ }
+
+ public static class MissingFieldException extends ImportException {
+ public MissingFieldException() { super(); }
+ public MissingFieldException(String message) { super(message); }
+ public String userMessage() { return "Unsupported value for field " + getMessage(); }
+ }
+
+ public static class ShortSequenceException extends ImportException {
+ public ShortSequenceException() { super(); }
+ public ShortSequenceException(String message) { super(message); }
+ public String userMessage() { return "Sequence is too short: " + getMessage(); }
+ }
+
+ public static class TooFewTaxaException extends ImportException {
+ public TooFewTaxaException() { super(); }
+ public TooFewTaxaException(String message) { super(message); }
+ public String userMessage() { return "Number of taxa is less than expected: " +
+ (getMessage() != null ? getMessage() : ""); }
+ }
+
+ public static class DuplicateTaxaException extends ImportException {
+ public DuplicateTaxaException() { super(); }
+ public DuplicateTaxaException(String message) { super(message); }
+ }
+
+ public static class UnknownTaxonException extends ImportException {
+ public UnknownTaxonException() { super(); }
+ public UnknownTaxonException(String message) { super(message); }
+ }
+
+}
diff --git a/src/jebl/evolution/io/ImportHelper.java b/src/jebl/evolution/io/ImportHelper.java
new file mode 100644
index 0000000..55edd4d
--- /dev/null
+++ b/src/jebl/evolution/io/ImportHelper.java
@@ -0,0 +1,746 @@
+/*
+ * ImportHelper.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.SequenceType;
+import jebl.util.ProgressListener;
+
+import java.io.*;
+
+/**
+ * A helper class for phylogenetic file format importers
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: ImportHelper.java 699 2007-04-28 07:07:49Z matthew_cheung $
+ */
+public class ImportHelper {
+ private long totalCharactersRead = 0;
+
+ // Expected length of input in bytes, or 0 if unknown
+ private long expectedInputLength = 0;
+
+ /**
+ * ATTENTION: The ImportHelper never closes the reader passed to the constructor.
+ * If the reader holds resources (e.g. a FileReader, which holds an open file),
+ * then it is the client class' responsibility to close the reader when it has
+ * finished using it.
+ * @param reader
+ */
+ public ImportHelper(Reader reader) {
+ this.reader = new LineNumberReader(reader);
+ this.commentWriter = null;
+ }
+
+ public void setExpectedInputLength(long l) {
+ this.expectedInputLength = l;
+ }
+
+ public ImportHelper(Reader reader, Writer commentWriter) {
+ this.reader = new LineNumberReader(reader);
+ this.commentWriter = new BufferedWriter(commentWriter);
+ }
+
+ /**
+ * @return If the length of the input is known (because a file was
+ * passed to the constructor), this reports a value between 0.0 and 1.0
+ * indicating the relative read position in the file. Otherwise, this
+ * always returns 0.0.
+ *
+ * This method assumes that all characters in the input are one byte
+ * long (to get its estimate, it divides the number of *characters* read
+ * by the number of *bytes* in the file). If there is an efficient way
+ * to fix this, we should do so :)
+ */
+ public double getProgress() {
+ if (expectedInputLength == 0) {
+ return 0.0;
+ } else {
+ return (double) totalCharactersRead / expectedInputLength;
+ }
+ }
+
+ public void closeReader() throws IOException {
+ reader.close();
+ }
+
+ public void setCommentDelimiters(char line) {
+ hasComments = true;
+ this.lineComment = line;
+ }
+
+ public void setCommentDelimiters(char start, char stop) {
+ hasComments = true;
+ this.startComment = start;
+ this.stopComment = stop;
+ }
+
+ public void setCommentDelimiters(char start, char stop, char line) {
+ hasComments = true;
+ this.startComment = start;
+ this.stopComment = stop;
+ this.lineComment = line;
+ }
+
+ public void setCommentDelimiters(char start, char stop, char line, char write, char meta) {
+ hasComments = true;
+ this.startComment = start;
+ this.stopComment = stop;
+ this.lineComment = line;
+ this.writeComment = write;
+ this.metaComment = meta;
+ }
+
+ public void setCommentWriter(Writer commentWriter) {
+ this.commentWriter = new BufferedWriter(commentWriter);
+ }
+
+ public int getLineNumber() {
+ return reader.getLineNumber();
+ }
+
+ public int getLastDelimiter() {
+ return lastDelimiter;
+ }
+
+ public char nextCharacter() throws IOException {
+ if (lastChar == '\0') {
+ lastChar = readCharacter();
+ }
+ return (char)lastChar;
+ }
+
+ public char readCharacter() throws IOException {
+
+ skipSpace();
+
+ char ch = read();
+
+ while (hasComments && (ch == startComment || ch == lineComment)) {
+ skipComments(ch);
+ skipSpace();
+ ch = read();
+ }
+
+ return ch;
+ }
+
+ public void unreadCharacter(char ch) {
+ lastChar = ch;
+ }
+
+ public char next() throws IOException {
+ if (lastChar == '\0') {
+ lastChar = read();
+ }
+ return (char)lastChar;
+ }
+
+ /**
+ * All read attempts pass through this function.
+ * @return
+ * @throws IOException
+ */
+ public char read() throws IOException {
+ int ch;
+ if (lastChar == '\0') {
+ // this is the only point where anything is read from the reader
+ ch = reader.read();
+ if (ch != -1) {
+ totalCharactersRead++;
+ } else {
+ throw new EOFException();
+ }
+ } else {
+ ch = lastChar;
+ lastChar = '\0';
+ }
+ return (char)ch;
+ }
+
+ /**
+ * Reads a line, skipping over any comments.
+ */
+ public String readLine() throws IOException {
+
+ StringBuffer line = new StringBuffer();
+ char ch = read();
+ try {
+ while (ch != '\n' && ch != '\r') {
+ if (hasComments) {
+ if (ch == lineComment) {
+ skipComments(ch);
+ break;
+ }
+ if (ch == startComment) {
+ skipComments(ch);
+ ch = read();
+ }
+ }
+ line.append(ch);
+ ch = read();
+ }
+
+ // accommodate DOS line endings..
+ if (ch == '\r') {
+ if (next() == '\n') read();
+ }
+ lastDelimiter = ch;
+
+ } catch (EOFException e) {
+ // We catch an EOF and return the line we have so far
+// encounteredEndOfFile();
+ }
+
+ return line.toString();
+ }
+
+ public void readSequence(StringBuilder sequence, SequenceType sequenceType,
+ String delimiters, int maxSites,
+ String gapCharacters, String missingCharacters,
+ String matchCharacters, String matchSequence) throws IOException, ImportException {
+ readSequence(sequence, sequenceType, delimiters, maxSites, gapCharacters, missingCharacters,
+ matchCharacters, matchSequence, ProgressListener.EMPTY);
+ }
+
+ public void readSequence(StringBuilder sequence, SequenceType sequenceType,
+ String delimiters, int maxSites,
+ String gapCharacters, String missingCharacters,
+ String matchCharacters, String matchSequence,
+ boolean stopAtDoubleNewLine) throws IOException, ImportException {
+ readSequence(sequence, sequenceType, delimiters, maxSites, gapCharacters, missingCharacters,
+ matchCharacters, matchSequence, ProgressListener.EMPTY, stopAtDoubleNewLine);
+ }
+
+ public void readSequence(StringBuilder sequence, SequenceType sequenceType,
+ String delimiters, int maxSites,
+ String gapCharacters, String missingCharacters,
+ String matchCharacters, String matchSequence,
+ ProgressListener progress)
+ throws IOException, ImportException
+ {
+ readSequence(sequence, sequenceType, delimiters, maxSites, gapCharacters, missingCharacters,
+ matchCharacters, matchSequence, progress, false);
+ }
+
+ /**
+ *
+ * Reads sequence, skipping over any comments and filtering using sequenceType.
+ * @param sequence a StringBuffer into which the sequence is put
+ * @param sequenceType the sequenceType of the sequence
+ * @param delimiters list of characters that will stop the reading
+ * @param gapCharacters list of characters that will be read as gaps
+ * @param missingCharacters list of characters that will be read as missing
+ * @param matchCharacters list of characters that will be read as matching the matchSequence
+ * @param matchSequence the sequence string to match match characters to
+ * @param maxSites maximum number of sites to read
+ * @param progress optional ProgressListener. Must not be null.
+ * @param stopAtDoubleNewLine if true will stop reading if it encounters two consectutive new line characters.
+ */
+ public void readSequence(StringBuilder sequence, SequenceType sequenceType,
+ String delimiters, int maxSites,
+ String gapCharacters, String missingCharacters,
+ String matchCharacters, String matchSequence,
+ ProgressListener progress, boolean stopAtDoubleNewLine)
+ throws IOException, ImportException
+ {
+ char ch = read();
+
+ final char gapCode = sequenceType.getGapState().getCode().charAt(0);
+ final char unknownCode = sequenceType.getUnknownState().getCode().charAt(0);
+
+ ByteBuilder bb = new ByteBuilder((int)expectedInputLength);
+
+ try {
+ int nSites = 0;
+ boolean doubleNewLine = false;
+
+ while (!(stopAtDoubleNewLine && doubleNewLine) && nSites < maxSites && delimiters.indexOf(ch) == -1) {
+ if((nSites%1024) == 0 && progress.setProgress(getProgress())) return;
+
+ if (hasComments && (ch == startComment || ch == lineComment)) {
+ skipComments(ch);
+ ch = read();
+ }
+
+ if (!Character.isWhitespace(ch)) {
+ if (gapCharacters.indexOf(ch) != -1) {
+ bb.append(gapCode);
+ } else if (missingCharacters.indexOf(ch) != -1) {
+ bb.append(unknownCode);
+ //sequence.append(unknownCode);
+ } else if (matchCharacters.indexOf(ch) != -1) {
+ if (matchSequence == null) {
+ throw new ImportException("Match character in first sequences");
+ }
+ if (nSites >= matchSequence.length()) {
+ throw new ImportException("Match sequences too short");
+ }
+
+ bb.append(matchSequence.charAt(nSites));
+ //sequence.append(matchSequence.charAt(n));
+ } else {
+ //sequence.append(ch);
+ bb.append(ch);
+ }
+ nSites++;
+ }
+ char previous = ch;
+ ch = read();
+ if(previous == '\n' && ch == '\n')
+ doubleNewLine = true;
+ }
+
+ lastDelimiter = ch;
+
+ if (Character.isWhitespace((char)lastDelimiter)) {
+ ch = nextCharacter();
+ if (delimiters.indexOf(ch) != -1) {
+ lastDelimiter = readCharacter();
+ }
+ }
+ } catch (EOFException e) {
+ // We catch an EOF and return the sequences we have so far
+ }
+ sequence.ensureCapacity(bb.length());
+ sequence.append(bb);
+ }
+
+ /**
+ * Reads a line of sequence, skipping over any comments and filtering using sequenceType.
+ * @param sequence a StringBuffer into which the sequence is put
+ * @param sequenceType the sequenceType of the sequence
+ * @param delimiters list of characters that will stop the reading
+ * @param gapCharacters list of characters that will be read as gaps
+ * @param missingCharacters list of characters that will be read as missing
+ * @param matchCharacters list of characters that will be read as matching the matchSequence
+ * @param matchSequence the sequence string to match match characters to
+ * @throws IOException
+ * @throws ImportException
+ */
+ public void readSequenceLine(StringBuffer sequence, SequenceType sequenceType,
+ String delimiters,
+ String gapCharacters, String missingCharacters,
+ String matchCharacters, String matchSequence) throws IOException, ImportException {
+
+ char ch = read();
+
+ try {
+ int n = 0;
+
+ while (ch != '\r' && ch != '\n' && delimiters.indexOf(ch) == -1) {
+
+ if (hasComments) {
+ if (ch == lineComment) {
+ skipComments(ch);
+ break;
+ }
+ if (ch == startComment) {
+ skipComments(ch);
+ ch = read();
+ // ch may be eol!!
+ continue;
+ }
+ }
+
+ if (ch != ' ' && ch != '\t') {
+ if (gapCharacters.indexOf(ch) != -1) {
+ sequence.append(sequenceType.getGapState().getCode());
+ } else if (missingCharacters.indexOf(ch) != -1) {
+ sequence.append(sequenceType.getUnknownState().getCode());
+ } else if (matchCharacters.indexOf(ch) != -1) {
+ if (matchSequence == null) {
+ throw new ImportException("Match character in first sequences");
+ }
+ if (n >= matchSequence.length()) {
+ throw new ImportException("Match sequences too short");
+ }
+
+ sequence.append(matchSequence.charAt(n));
+ } else {
+ sequence.append(ch);
+ }
+ n++;
+ }
+
+ ch = read();
+ }
+
+ if (ch == '\r') {
+ if (next() == '\n') read();
+ }
+
+ lastDelimiter = ch;
+
+ if (Character.isWhitespace((char)lastDelimiter)) {
+ ch = nextCharacter();
+ if (delimiters.indexOf(ch) != -1) {
+ lastDelimiter = readCharacter();
+ }
+ }
+
+ } catch (EOFException e) {
+ // We catch an EOF and return the sequences we have so far
+ }
+ }
+
+ /**
+ * Attempts to read and parse an integer delimited by whitespace.
+ */
+ public int readInteger() throws IOException, ImportException {
+ String token = readToken();
+ try {
+ return Integer.parseInt(token);
+ } catch (NumberFormatException nfe) {
+ throw new ImportException("Number format error: " + nfe.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to read and parse an integer delimited by whitespace or by
+ * any character in delimiters.
+ */
+ public int readInteger(String delimiters) throws IOException, ImportException {
+ String token = readToken(delimiters);
+ try {
+ return Integer.parseInt(token);
+ } catch (NumberFormatException nfe) {
+ throw new ImportException("Number format error: " + nfe.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to read and parse a double delimited by whitespace.
+ */
+ public double readDouble() throws IOException, ImportException {
+ String token = readToken();
+ try {
+ return Double.parseDouble(token);
+ } catch (NumberFormatException nfe) {
+ throw new ImportException("Number format error: " + nfe.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to read and parse a double delimited by whitespace or by
+ * any character in delimiters.
+ */
+ public double readDouble(String delimiters) throws IOException, ImportException {
+ String token = readToken(delimiters);
+ try {
+ return Double.parseDouble(token);
+ } catch (NumberFormatException nfe) {
+ throw new ImportException("Number format error: " + nfe.getMessage());
+ }
+ }
+
+ /**
+ * Reads a token stopping when any whitespace or a comment is found.
+ * If the token begins with a quote char then all characters will be
+ * included in token until a matching quote is found (including whitespace or comments).
+ */
+ public String readToken() throws IOException {
+ return readToken("");
+ }
+
+ /**
+ * Reads a token stopping when any whitespace, a comment or when any character
+ * in delimiters is found. If the token begins with a quote char
+ * then all characters will be included in token until a matching
+ * quote is found (including whitespace or comments).
+ */
+ public String readToken(String delimiters) throws IOException {
+ int space = 0;
+ char ch, ch2, quoteChar = '\0';
+ boolean done = false, first = true, quoted = false, isSpace;
+
+ nextCharacter();
+
+ StringBuffer token = new StringBuffer();
+
+ while (!done) {
+ ch = read();
+
+ try {
+ isSpace = Character.isWhitespace(ch);
+
+ if (quoted && ch == quoteChar) { // Found the closing quote
+ ch2 = read();
+
+ if (ch == ch2) {
+ // A repeated quote character so add this to the token
+ token.append(ch);
+ } else {
+ // otherwise it terminates the token
+
+ lastDelimiter = ' ';
+
+ if (hasComments && (ch2 == startComment || ch2 == lineComment)) {
+ skipComments(ch2, startComment!= '\"' && startComment != '\'');
+ } else {
+ unreadCharacter(ch2);
+ }
+ done = true;
+ quoted = false;
+ }
+ } else if (first && (ch == '\'' || ch == '"')) {
+ // if the opening character is a quote
+ // read everything up to the closing quote
+ quoted = true;
+ quoteChar = ch;
+ first = false;
+ space = 0;
+ } else if (!quoted && (ch == startComment || ch == lineComment) ) {
+ // comment markers don't count if we are quoted
+ skipComments(ch, startComment!= '\"' && startComment != '\'');
+ lastDelimiter = ' ';
+ done = true;
+ } else {
+ if (quoted) {
+ token.append(ch);
+ } else if (isSpace) {
+ lastDelimiter = ' ';
+ done = true;
+ } else if (delimiters.indexOf(ch) != -1) {
+ done = true;
+ lastDelimiter = ch;
+ } else {
+ token.append(ch);
+ first = false;
+ }
+ }
+ } catch (EOFException e) {
+ // We catch an EOF and return the token we have so far
+ done = true;
+ }
+ }
+
+ if (Character.isWhitespace((char)lastDelimiter)) {
+ ch = nextCharacter();
+ while (Character.isWhitespace(ch)) {
+ read();
+ ch = nextCharacter();
+ }
+
+ if (delimiters.indexOf(ch) != -1) {
+ lastDelimiter = readCharacter();
+ }
+ }
+
+ return token.toString();
+ }
+
+ /**
+ * Skips over any comments. The opening comment delimiter is passed.
+ * @param delimiter
+ * @throws java.io.IOException
+ */
+ protected void skipComments(char delimiter) throws IOException {
+ skipComments(delimiter, false);
+ }
+
+ /**
+ * Skips over any comments. The opening comment delimiter is passed.
+ * @param delimiter
+ * @param gobbleStrings
+ * @throws java.io.IOException
+ */
+ protected void skipComments(char delimiter, boolean gobbleStrings) throws IOException {
+
+ char ch;
+ int n=1;
+ boolean write = false;
+ StringBuffer meta = null;
+
+ if (nextCharacter() == writeComment) {
+ read();
+ write = true;
+ } else if (nextCharacter() == metaComment) {
+ read();
+ meta = new StringBuffer();
+ }
+
+ lastMetaComment = null;
+
+ if (delimiter == lineComment) {
+ String line = readLine();
+ if (write && commentWriter != null) {
+ commentWriter.write(line, 0, line.length());
+ commentWriter.newLine();
+ } else if (meta != null) {
+ meta.append(line);
+ }
+ } else {
+ Character inString = null;
+ do {
+ ch = read();
+
+ if( ch == '\"' || ch == '\'' ) {
+ if( gobbleStrings ) {
+ if( inString == null ) {
+ inString = ch;
+ } else if( inString == ch ) {
+ inString = null;
+ }
+ }
+ }
+ if( inString == null ) {
+ if (ch == startComment) {
+ n++;
+ continue;
+ } else if (ch == stopComment) {
+ if (write && commentWriter != null) {
+ commentWriter.newLine();
+ }
+ n--;
+ continue;
+ }
+ }
+
+ if (write && commentWriter != null) {
+ commentWriter.write(ch);
+ } else if (meta != null) {
+ meta.append(ch);
+ }
+ } while (n > 0);
+
+ }
+
+ if (meta != null) {
+ lastMetaComment = meta.toString();
+ }
+ }
+
+ /**
+ * Skips to the end of the line. If a comment is found then this is read.
+ */
+ public void skipToEndOfLine() throws IOException {
+
+ char ch;
+
+ do {
+ ch = read();
+ if (hasComments) {
+ if (ch == lineComment) {
+ skipComments(ch);
+ break;
+ }
+ if (ch == startComment) {
+ skipComments(ch);
+ ch = read();
+ }
+ }
+
+ } while (ch != '\n' && ch != '\r');
+
+ if (ch == '\r') {
+ if (nextCharacter() == '\n') read();
+ }
+ }
+
+ /**
+ * Skips char any contiguous characters in skip. Will also skip
+ * comments.
+ */
+ public void skipWhile(String skip) throws IOException {
+
+ char ch;
+
+ do {
+ ch = read();
+ } while ( skip.indexOf(ch) > -1 );
+
+ unreadCharacter(ch);
+ }
+
+ /**
+ * Skips over any space (plus tabs and returns) in the file. Will also skip
+ * comments.
+ */
+ public void skipSpace() throws IOException {
+ skipWhile(" \t\r\n");
+ }
+
+ /**
+ * Skips over any contiguous characters in skip. Will also skip
+ * comments and space.
+ */
+ public void skipCharacters(String skip) throws IOException {
+ skipWhile(skip + " \t\r\n");
+ }
+
+ /**
+ * Skips over the file until a character from delimiters is found. Returns
+ * the delimiter found. Will skip comments and will ignore delimiters within
+ * comments.
+ */
+ public char skipUntil(String skip) throws IOException {
+ char ch;
+
+ do {
+ ch = readCharacter();
+ } while ( skip.indexOf(ch) == -1 );
+
+ return ch;
+ }
+
+ public String getLastMetaComment() {
+ return lastMetaComment;
+ }
+
+ public void clearLastMetaComment() {
+ lastMetaComment = null;
+ }
+
+ static String safeName(String name) {
+ if( ! name.matches("[a-zA-Z0-9_.]+") ) {
+ name = "\"" + name + "\"";
+ }
+ return name;
+ }
+
+ /**
+ * Convert control (unprintable) characters to something printable
+ * @param token
+ * @return token printable version
+ */
+ static String convertControlsChars(String token) {
+ if( ! token.matches("[^\\p{Cntrl}]+") ) {
+ StringBuilder b = new StringBuilder();
+ for( char c : token.toCharArray() ) {
+ if( c < 0x20 || c >= 0xfe ) {
+ b.append("#").append(Integer.toHexString(c));
+ } else {
+ b.append(c);
+ }
+ }
+ return b.toString();
+ }
+ return token;
+ }
+
+ // Private stuff
+
+ private LineNumberReader reader;
+ private BufferedWriter commentWriter = null;
+
+ private int lastChar = '\0';
+ private int lastDelimiter = '\0';
+
+ private boolean hasComments = false;
+ private char startComment = (char)-1;
+ private char stopComment = (char)-1;
+ private char lineComment = (char)-1;
+ private char writeComment = (char)-1;
+ private char metaComment = (char)-1;
+
+ private String lastMetaComment = null;
+}
diff --git a/src/jebl/evolution/io/MEGAExporter.java b/src/jebl/evolution/io/MEGAExporter.java
new file mode 100644
index 0000000..22f9a78
--- /dev/null
+++ b/src/jebl/evolution/io/MEGAExporter.java
@@ -0,0 +1,66 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * * Export to MEGA.
+ *
+ * @author Joseph Heled
+ * @version $Id: MEGAExporter.java 530 2006-11-15 04:01:44Z stevensh $
+ */
+public class MEGAExporter implements AlignmentExporter {
+ private PrintWriter writer;
+
+ /**
+ *
+ * @param writer where export text goes
+ */
+ public MEGAExporter(Writer writer, String comment) {
+ this.writer = new PrintWriter(writer);
+ this.writer.println("#mega");
+ if( comment != null ) {
+ this.writer.println("!" + comment);
+ }
+ }
+
+ /**
+ *
+ * @param alignment the alignment to export
+ * @param name the name of the alignment
+ * @throws IOException
+ */
+ public void exportAlignment(Alignment alignment, String name) throws IOException{
+ writer.print("!Title ");
+ writer.print(name);
+ writer.println(";");
+
+ writer.print("!Format DataType=");
+ String dataType =
+ alignment.getSequenceType() == SequenceType.NUCLEOTIDE?
+ "nucleotide": "protein";
+ writer.println(dataType + ";");
+ exportAlignment(alignment);
+ }
+
+ /**
+ * @deprecated Files created by this export method won't be importable by MEGA (because they don't have titles). Use {@link #exportAlignment(jebl.evolution.alignments.Alignment, String)} instead.
+ * @param alignment
+ * @throws IOException
+ */
+ public void exportAlignment(Alignment alignment) throws IOException {
+ List<Sequence> seqs = alignment.getSequenceList();
+
+ for( Sequence seq : seqs ) {
+ writer.println();
+ writer.println("#" + seq.getTaxon().getName().replaceAll(" ","_"));
+ writer.println(seq.getString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/io/NewickExporter.java b/src/jebl/evolution/io/NewickExporter.java
new file mode 100644
index 0000000..5c6044c
--- /dev/null
+++ b/src/jebl/evolution/io/NewickExporter.java
@@ -0,0 +1,56 @@
+package jebl.evolution.io;
+
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+import jebl.evolution.trees.RootedTree;
+
+import java.io.Writer;
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: NewickExporter.java 429 2006-08-26 18:17:39Z rambaut $
+ */
+public class NewickExporter implements TreeExporter {
+ public NewickExporter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /**
+ * Export a single tree
+ *
+ * @param tree
+ * @throws java.io.IOException
+ */
+ public void exportTree(Tree tree) throws IOException {
+ writeTree(tree);
+ }
+
+ /**
+ * Export a collection of trees
+ *
+ * @param trees
+ * @throws java.io.IOException
+ */
+ public void exportTrees(Collection<? extends Tree> trees) throws IOException {
+ for (Tree tree : trees) {
+ writeTreeMine(tree);
+ }
+ }
+
+ private void writeTree(Tree tree) throws IOException {
+ writer.write(Utils.toNewick(Utils.rootTheTree(tree)));
+ }
+
+ /*
+ * edit for FileWriter
+ */
+ private void writeTreeMine(Tree tree) throws IOException {
+ writer.write(Utils.toNewick(Utils.rootTheTree(tree))+";\n");
+ }
+
+ private final Writer writer;
+}
diff --git a/src/jebl/evolution/io/NewickImporter.java b/src/jebl/evolution/io/NewickImporter.java
new file mode 100644
index 0000000..3e89d50
--- /dev/null
+++ b/src/jebl/evolution/io/NewickImporter.java
@@ -0,0 +1,220 @@
+package jebl.evolution.io;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.SimpleRootedTree;
+import jebl.evolution.trees.Tree;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: NewickImporter.java 557 2006-12-06 05:53:41Z msuchard $
+ */
+public class NewickImporter implements TreeImporter {
+
+ /**
+ * Constructor
+ * @param reader tree text
+ * @param unquotedLabels if true, try to read unqouted lables containing spaces
+ */
+ public NewickImporter(Reader reader, boolean unquotedLabels) {
+ helper = new ImportHelper(reader);
+ this.unquotedLabels = unquotedLabels;
+ }
+
+ /**
+ * Returns an iterator over a set of elements of type T.
+ *
+ * @return an Iterator.
+ */
+ public Iterator<Tree> iterator() {
+ return new Iterator<Tree>() {
+
+ public boolean hasNext() {
+ boolean hasNext = false;
+ try {
+ hasNext = hasTree();
+ } catch (IOException e) {
+ // deal with errors by stopping the iteration
+ } catch (ImportException e) {
+ // deal with errors by stopping the iteration
+ }
+ return hasNext;
+ }
+
+ public Tree next() {
+ Tree tree = null;
+ try {
+ tree = importNextTree();
+ } catch (IOException e) {
+ // deal with errors by stopping the iteration
+ } catch (ImportException e) {
+ // deal with errors by stopping the iteration
+ }
+ if (tree == null) throw new NoSuchElementException("No more trees in this file");
+ return tree;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("operation is not supported by this Iterator");
+ }
+ };
+ }
+
+ public boolean hasTree() throws IOException, ImportException {
+ try {
+ helper.skipUntil("(");
+ helper.unreadCharacter('(');
+ } catch (EOFException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public Tree importNextTree() throws IOException, ImportException {
+
+ try {
+ helper.skipUntil("(");
+ helper.unreadCharacter('(');
+
+ return readTree();
+ } catch (EOFException e) {
+ //
+ throw new ImportException("error");
+ }
+ }
+
+ public List<Tree> importTrees() throws IOException, ImportException {
+ List<Tree> trees = new ArrayList<Tree>();
+
+ while (hasTree()) {
+ final Tree t = importNextTree();
+ if( t != null ) {
+ trees.add(t);
+ }
+ }
+
+ return trees;
+ }
+
+ private RootedTree readTree() throws IOException, ImportException {
+ SimpleRootedTree tree = new SimpleRootedTree();
+
+ readInternalNode(tree);
+
+ return tree;
+ }
+
+ /**
+ * Reads a branch in. This could be a node or a tip (calls readNode or readTip
+ * accordingly). It then reads the branch length and SimpleNode that will
+ * point at the new node or tip.
+ */
+ private Node readBranch(SimpleRootedTree tree) throws IOException, ImportException
+ {
+ Node branch;
+
+ if (helper.nextCharacter() == '(') {
+ // is an internal node
+ branch = readInternalNode(tree);
+
+ } else {
+ // is an external node
+ branch = readExternalNode(tree);
+ }
+
+ if (helper.getLastDelimiter() == ':') {
+ double length = helper.readDouble(",():;");
+ tree.setLength(branch, length);
+ } else {
+ tree.setLength(branch, 1.0);
+ }
+
+ return branch;
+ }
+
+ /**
+ * Reads a node in. This could be a polytomy. Calls readBranch on each branch
+ * in the node.
+ */
+ private Node readInternalNode(SimpleRootedTree tree) throws IOException, ImportException
+ {
+ List<Node> children = new ArrayList<Node>();
+
+ // read the opening '('
+ helper.readCharacter();
+
+ // read the first child
+ children.add( readBranch(tree) );
+
+ // an internal node must have at least 2 children
+ if (helper.getLastDelimiter() != ',') {
+ throw new ImportException.BadFormatException("Missing ',' in tree");
+ }
+
+ // read subsequent children
+ do {
+ children.add( readBranch(tree) );
+
+ } while (helper.getLastDelimiter() == ',');
+
+ // should have had a closing ')'
+ if (helper.getLastDelimiter() != ')') {
+ throw new ImportException.BadFormatException("Missing closing ')' in tree");
+ }
+
+ final Node node = tree.createInternalNode(children);
+
+ try {
+ // find the next delimiter
+ String token = helper.readToken(":(),;");
+
+ if (token.length() > 0) {
+ node.setAttribute("label", NexusImporter.parseValue(token));
+ }
+
+ // If there is a metacomment before the branch length indicator (:), then it is a node attribute
+ if (helper.getLastMetaComment() != null) {
+ // There was a meta-comment which should be in the form:
+ // \[&label[=value][,label[=value]>[,/..]]\]
+ NexusImporter.parseMetaCommentPairs(helper.getLastMetaComment(), node);
+
+ helper.clearLastMetaComment();
+ }
+
+ } catch( EOFException e) {
+ // Ok if we just finished
+ }
+
+ return node;
+ }
+
+ /**
+ * Reads an external node in.
+ */
+ private Node readExternalNode(SimpleRootedTree tree) throws IOException, ImportException
+ {
+ String label = helper.readToken(":(),;");
+ while( unquotedLabels && helper.getLastDelimiter() == ' ' ) {
+ label = label + " " + helper.readToken(":(),;");
+ }
+ try {
+ return tree.createExternalNode(Taxon.getTaxon(label));
+ } catch (IllegalArgumentException e) {
+ throw new ImportException.DuplicateTaxaException(e.getMessage());
+ }
+ }
+
+ private final ImportHelper helper;
+ private boolean unquotedLabels;
+}
diff --git a/src/jebl/evolution/io/NexusExporter.java b/src/jebl/evolution/io/NexusExporter.java
new file mode 100644
index 0000000..94bd559
--- /dev/null
+++ b/src/jebl/evolution/io/NexusExporter.java
@@ -0,0 +1,386 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+import jebl.util.Attributable;
+
+import java.awt.*;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.*;
+import java.util.List;
+
+/**
+ * Export sequences and trees to Nexus format.
+ *
+ * @author Joseph Heled
+ *
+ * @version $Id: NexusExporter.java 730 2007-06-17 22:11:34Z matt_kearse $
+ */
+
+public class NexusExporter implements AlignmentExporter, SequenceExporter, TreeExporter {
+ /**
+ *
+ * @param writer where export text goes
+ */
+ public NexusExporter(Writer writer) {
+ this.writer = new PrintWriter(writer);
+ this.writer.println("#NEXUS");
+ }
+
+ /**
+ * exportAlignment.
+ */
+ public void exportAlignment(Alignment alignment) throws IOException {
+ exportSequences(alignment.getSequences());
+ }
+
+ /**
+ * export alignment.
+ */
+ public void exportSequences(Collection<? extends Sequence> sequences) throws IOException, IllegalArgumentException {
+
+ establishSequenceTaxa(sequences);
+
+ SequenceType seqType = null;
+
+ int maxLength = 0;
+ for (Sequence sequence : sequences) {
+ if (sequence.getLength() > maxLength) {
+ maxLength = sequence.getLength();
+ }
+ if (seqType == null) {
+ seqType = sequence.getSequenceType();
+ } else if( seqType != sequence.getSequenceType() ) {
+ throw new IllegalArgumentException("All seqeuences must have the same type");
+ }
+ }
+
+ writer.println("begin characters;");
+ writer.println("\tdimensions nchar=" + maxLength + ";");
+ if( seqType != null ) {
+ writer.println("\tformat datatype=" + seqType.getNexusDataType() +
+ " missing=" + seqType.getUnknownState().getName() +
+ " gap=" + seqType.getGapState().getCode() + ";");
+
+ writer.println("\tmatrix");
+ for (Sequence sequence : sequences) {
+
+
+ if( sequence.getSequenceType() != seqType ) {
+ throw new IllegalArgumentException("SequenceTypes of sequences in collection do not match");
+ }
+ StringBuilder builder = new StringBuilder("\t");
+ appendTaxonName(sequence.getTaxon(), builder);
+ builder.append("\t").append(sequence.getString());
+ int shortBy = maxLength - sequence.getLength();
+ if (shortBy > 0) {
+ for (int i = 0; i < shortBy; i++) {
+ builder.append(seqType.getGapState().getCode());
+ }
+ }
+ writer.println(builder);
+ }
+ writer.println(";\nend;");
+ }
+ }
+
+ /**
+ * Export a single tree
+ *
+ * @param tree
+ * @throws java.io.IOException
+ */
+ public void exportTree(Tree tree) throws IOException {
+ List<Tree> trees = new ArrayList<Tree>();
+ trees.add(tree);
+ exportTrees(trees);
+ }
+
+ private void writeTrees(Collection<? extends Tree> trees, boolean checkTaxa) throws IOException {
+
+ int nt = 0;
+ for( Tree t : trees ) {
+ if( checkTaxa && establishTreeTaxa(t) ) {
+ throw new IllegalArgumentException();
+ }
+ final boolean isRooted = t instanceof RootedTree;
+ final RootedTree rtree = isRooted ? (RootedTree)t : Utils.rootTheTree(t);
+
+ final Object name = t.getAttribute(treeNameAttributeKey);
+
+ ++nt;
+ final String treeName = (name != null) ? name.toString() : "tree_" + nt;
+
+ StringBuilder builder = new StringBuilder("\ttree [&");
+
+ // TREE & UTREE are depreciated in the NEXUS format in favour of a metacomment
+ // [&U] or [&R] after the TREE command. Andrew.
+ builder.append(isRooted && !rtree.conceptuallyUnrooted() ? "r] " : "u] ");
+ builder.append(treeName);
+ builder.append(" = ");
+
+ appendAttributes(rtree, treeNameAttributeKey, builder);
+
+ appendTree(rtree, rtree.getRootNode(), builder);
+ builder.append(";");
+
+ writer.println(builder);
+ }
+ }
+ /**
+ * export trees
+ */
+ public void exportTrees(Collection<? extends Tree> trees) throws IOException {
+ // all trees in a set should have the same taxa
+ establishTreeTaxa(trees.iterator().next());
+ writer.println("begin trees;");
+ writeTrees(trees, true);
+ writer.println("end;");
+ }
+
+ public void exportTreesWithTranslation(Collection<? extends Tree> trees, Map<String, String> t) throws IOException {
+ writer.println("begin trees;");
+ writer.println("\ttranslate");
+ boolean first = true;
+ for( Map.Entry<String, String> e : t.entrySet() ) {
+ writer.print((first ? "" : ",\n") + "\t\t" + safeName(e.getKey()) + " " + safeName(e.getValue()));
+ first = false;
+ }
+ writer.println("\n\t;");
+
+ writeTrees(trees, false);
+ writer.println("end;");
+ }
+
+ public void exportMatrix(final DistanceMatrix distanceMatrix) {
+ final List<Taxon> taxa = distanceMatrix.getTaxa();
+ establishTaxa(taxa);
+ writer.println("begin distances;");
+ // assume distance matrix is symetric, so save upper part. no method to guarantee this yet
+ final double[][] distances = distanceMatrix.getDistances();
+ writer.println(" format triangle = upper nodiagonal;");
+ writer.println(" matrix ");
+ for(int i = 0; i < taxa.size(); ++i) {
+ StringBuilder builder = new StringBuilder("\t");
+ appendTaxonName(taxa.get(i), builder);
+ for(int j = i+1; j < taxa.size(); ++j) {
+ builder.append(" ");
+ builder.append(distances[i][j]);
+ }
+ writer.println(builder);
+ }
+ writer.println(";");
+ writer.println("end;");
+ }
+
+ /**
+ * Write a new taxa block and record them for later reference.
+ * @param taxons
+ */
+ private void setTaxa(Taxon[] taxons) {
+ taxa = new HashSet<Taxon>();
+
+ writer.println("begin taxa;");
+ writer.println("\tdimensions ntax=" + taxons.length + ";");
+ writer.println("\ttaxlabels");
+
+ for (Taxon taxon : taxons) {
+ taxa.add(taxon);
+
+ StringBuilder builder = new StringBuilder("\t");
+ appendTaxonName(taxon, builder);
+ appendAttributes(taxon, null, builder);
+ writer.println(builder);
+ }
+ writer.println(";\nend;\n");
+ }
+
+ final private String nameRegex = "^(\\w|-)+$";
+
+ /**
+ * Name suitable as token - quotes if necessary
+ * @param name to check
+ * @return the name
+ */
+ private String safeName(String name) {
+ // allow dash in names
+
+ if (!name.matches(nameRegex)) {
+ name = name.replace("\'", "\'\'");
+ return "\'" + name + "\'";
+ }
+ return name;
+ }
+
+ /**
+ * name suitable for printing - quotes if necessary
+ * @param taxon
+ * @param builder
+ * @return
+ */
+ private StringBuilder appendTaxonName(Taxon taxon, StringBuilder builder) {
+ String name = taxon.getName();
+ if (!name.matches(nameRegex)) {
+ // JEBL way of quoting the quote character
+ name = name.replace("\'", "\'\'");
+ builder.append("\'").append(name).append("\'");
+ return builder;
+ }
+ return builder.append(name);
+ }
+
+ /**
+ * Prepare for writing an alignment. If a taxa block exists and is suitable for alignment,
+ * do nothing. If not, write a new taxa block.
+ * @param sequences
+ */
+ private void establishSequenceTaxa(Collection<? extends Sequence> sequences) {
+ if( taxa != null && taxa.size() == sequences.size() ) {
+ boolean hasAll = true;
+ for( Sequence s : sequences ) {
+ if( taxa.contains(s.getTaxon()) ) {
+ hasAll = false;
+ break;
+ }
+ }
+ if( hasAll ) {
+ return;
+ }
+ }
+
+ List<Taxon> t = new ArrayList<Taxon>(sequences.size());
+ for (Sequence sequence : sequences) {
+ t.add(sequence.getTaxon());
+ }
+ setTaxa(t.toArray(new Taxon[]{}));
+ }
+
+ private boolean establishTreeTaxa(Tree tree) {
+ return establishTaxa(tree.getTaxa());
+ }
+
+ private boolean establishTaxa(Collection<? extends Taxon> ntaxa) {
+ if( taxa != null && taxa.size() == ntaxa.size() && taxa.containsAll(ntaxa)) {
+ return false;
+ }
+
+ setTaxa(ntaxa.toArray(new Taxon[]{}));
+ return true;
+ }
+
+ /**
+ * Prepare for writing a tree. If a taxa block exists and is suitable for tree,
+ * do nothing. If not, write a new taxa block.
+ * @param tree
+ * @param node
+ * @param builder
+ */
+ private void appendTree(RootedTree tree, Node node, StringBuilder builder) {
+ if (tree.isExternal(node)) {
+ appendTaxonName(tree.getTaxon(node), builder);
+
+ appendAttributes(node, null, builder);
+
+ if( tree.hasLengths() ) {
+ builder.append(':');
+ builder.append(tree.getLength(node));
+ }
+ } else {
+ builder.append('(');
+ List<Node> children = tree.getChildren(node);
+ final int last = children.size() - 1;
+ for (int i = 0; i < children.size(); i++) {
+ appendTree(tree, children.get(i), builder);
+ builder.append(i == last ? ')' : ',');
+ }
+
+ appendAttributes(node, null, builder);
+
+ Node parent = tree.getParent(node);
+ // Don't write root length. This is ignored elsewhere and the nexus importer fails
+ // whet it is present.
+ if (parent != null) {
+ if (tree.hasLengths()) {
+ builder.append(":").append(tree.getLength(node));
+ }
+ }
+ }
+ }
+
+ private StringBuilder appendAttributes(Attributable item, String excludeKey, StringBuilder builder) {
+ boolean first = true;
+ for( String key : item.getAttributeNames() ) {
+ // we should replace the explicit check for name by something more general.
+ // Like a reserved character at the start (here &). however we have to worry about backward
+ // compatibility so no change yet with name.
+ if( (excludeKey == null || !key.equals(excludeKey)) && !key.startsWith("&") ) {
+ if (first) {
+ builder.append("[&");
+ first = false;
+ } else {
+ builder.append(",");
+ }
+
+ if( key.indexOf(' ') < 0 ) {
+ builder.append(key);
+ } else {
+ builder.append("\"").append(key).append("\"");
+ }
+
+ builder.append('=');
+
+ Object value = item.getAttribute(key);
+ appendAttributeValue(value, builder);
+ }
+ }
+ if (!first) {
+ builder.append("]");
+ }
+
+ return builder;
+ }
+
+ private StringBuilder appendAttributeValue(Object value, StringBuilder builder) {
+ if (value instanceof Object[]) {
+ builder.append("{");
+ Object[] elements = ((Object[])value);
+
+ if (elements.length > 0) {
+ appendAttributeValue(elements[0], builder);
+ for (int i = 1; i < elements.length; i++) {
+ builder.append(",");
+ appendAttributeValue(elements[i], builder);
+ }
+ }
+ return builder.append("}");
+ }
+
+ if (value instanceof Color) {
+ return builder.append("#").append(((Color)value).getRGB());
+ }
+
+ if (value instanceof String) {
+ return builder.append("\"").append(value).append("\"");
+ }
+
+ return builder.append(value);
+ }
+
+ static public String treeNameAttributeKey = "name";
+
+ static public boolean isGeneratedTreeName(String name) {
+ return name != null && name.matches("tree_[0-9]+");
+ }
+
+ private Set<Taxon> taxa = null;
+ protected final PrintWriter writer;
+}
diff --git a/src/jebl/evolution/io/NexusImporter.java b/src/jebl/evolution/io/NexusImporter.java
new file mode 100644
index 0000000..be86676
--- /dev/null
+++ b/src/jebl/evolution/io/NexusImporter.java
@@ -0,0 +1,1383 @@
+/*
+ * NexusImporter.java
+ *
+ * (c) 2002-2005 JEBL development team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.alignments.BasicAlignment;
+import jebl.evolution.distances.BasicDistanceMatrix;
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.CompactRootedTree;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.SimpleRootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.util.Attributable;
+
+import java.awt.*;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.*;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class for importing NEXUS file format
+ *
+ * @version $Id: NexusImporter.java 723 2007-06-11 05:40:44Z matt_kearse $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ */
+public class NexusImporter implements AlignmentImporter, SequenceImporter, TreeImporter, DistanceMatrixImporter {
+
+ public enum NexusBlock {
+ UNKNOWN,
+ TAXA,
+ CHARACTERS,
+ DATA,
+ UNALIGNED,
+ DISTANCES,
+ TREES
+ }
+
+ private boolean compactTrees = false;
+
+ // NEXUS specific ImportException classes
+ public static class MissingBlockException extends ImportException {
+ public MissingBlockException() { super(); }
+ public MissingBlockException(String message) { super(message); }
+ }
+
+ /**
+ * Constructor
+ */
+ public NexusImporter(Reader reader, long expectedLength) {
+ helper = new ImportHelper(reader);
+ helper.setExpectedInputLength(expectedLength);
+ initHelper();
+ }
+
+ /**
+ * Constructor
+ */
+ public NexusImporter(Reader reader) {
+ this(reader, 0);
+ }
+
+ public NexusImporter(Reader reader, boolean compactTrees, long expectedInputLength) {
+ this(reader, expectedInputLength);
+ this.compactTrees = compactTrees;
+ }
+
+ /**
+ * @param reader
+ * @param compactTrees
+ * @deprecated Use NexusImporter(Reader reader, boolean compactTrees, long expectedInputLength)
+ */
+ public NexusImporter(Reader reader, boolean compactTrees) {
+ // a wild guess on the low side
+ this(reader, compactTrees, 4*1024);
+ }
+
+ private void initHelper() {
+ // ! defines a comment to be written out to a log file
+ // & defines a meta comment
+ helper.setCommentDelimiters('[', ']', '\0', '!', '&');
+ }
+
+ /**
+ * This function returns an integer to specify what the
+ * next block in the file is. The internal variable nextBlock is also set to this
+ * value. This should be overridden to provide support for other blocks. Once
+ * the block is read in, nextBlock is automatically set to UNKNOWN_BLOCK by
+ * findEndBlock.
+ */
+ public NexusBlock findNextBlock() throws IOException
+ {
+ findToken("BEGIN", true);
+ nextBlockName = helper.readToken(";").toUpperCase();
+ return findBlockName(nextBlockName);
+ }
+
+ /**
+ * This function returns an enum class to specify what the
+ * block given by blockName is.
+ */
+ private NexusBlock findBlockName(String blockName)
+ {
+ try {
+ nextBlock = NexusBlock.valueOf(blockName);
+ } catch( IllegalArgumentException e ) {
+ // handle unknown blocks. java 1.5 throws an exception in valueOf
+ nextBlock = null;
+ }
+
+ if (nextBlock == null) {
+ nextBlock = NexusBlock.UNKNOWN;
+ }
+
+ return nextBlock;
+ }
+
+ public String getNextBlockName() {
+ return nextBlockName;
+ }
+
+ /**
+ * Returns an iterator over a set of elements of type T.
+ *
+ * @return an Iterator.
+ */
+ public Iterator<Tree> iterator() {
+ return new Iterator<Tree>() {
+
+ public boolean hasNext() {
+ boolean hasNext = false;
+ try {
+ hasNext = hasTree();
+ } catch (IOException e) {
+ // deal with errors by stopping the iteration
+ } catch (ImportException e) {
+ // deal with errors by stopping the iteration
+ }
+ return hasNext;
+ }
+
+ public Tree next() {
+ Tree tree = null;
+ try {
+ tree = importNextTree();
+ } catch (IOException e) {
+ // deal with errors by stopping the iteration
+ } catch (ImportException e) {
+ // deal with errors by stopping the iteration
+ }
+ if (tree == null) throw new NoSuchElementException("No more trees in this file");
+ return tree;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("operation is not supported by this Iterator");
+ }
+ };
+ }
+
+ /**
+ * Parses a 'TAXA' block.
+ */
+ public List<Taxon> parseTaxaBlock() throws ImportException, IOException
+ {
+ return readTaxaBlock();
+ }
+
+ /**
+ * Parses a 'CHARACTERS' block.
+ */
+ public List<Sequence> parseCharactersBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ return readCharactersBlock(taxonList);
+ }
+
+ /**
+ * Parses a 'DATA' block.
+ */
+ public List<Sequence> parseDataBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ return readDataBlock(taxonList);
+ }
+
+ /**
+ * Parses a 'TREES' block.
+ */
+ public List<Tree> parseTreesBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ return readTreesBlock(taxonList);
+ }
+
+ public DistanceMatrix parseDistancesBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ return readDistancesBlock(taxonList);
+ }
+
+ // **************************************************************
+ // SequenceImporter IMPLEMENTATION
+ // **************************************************************
+
+ /**
+ * importAlignment.
+ */
+ public List<Alignment> importAlignments() throws IOException, ImportException
+ {
+ boolean done = false;
+
+ List<Taxon> taxonList = null;
+ List<Alignment> alignments = new ArrayList<Alignment>();
+
+ while (!done) {
+ try {
+
+ NexusBlock block = findNextBlock();
+
+ if (block == NexusBlock.TAXA) {
+
+ taxonList = readTaxaBlock();
+
+ } else if (block == NexusBlock.CHARACTERS) {
+
+ if (taxonList == null) {
+ throw new MissingBlockException("TAXA block is missing");
+ }
+
+ List<Sequence> sequences = readCharactersBlock(taxonList);
+ alignments.add(new BasicAlignment(sequences));
+
+ } else if (block == NexusBlock.DATA) {
+
+ // A data block doesn't need a taxon block before it
+ // but if one exists then it will use it.
+ List<Sequence> sequences = readDataBlock(taxonList);
+ alignments.add(new BasicAlignment(sequences));
+
+ } else {
+ // Ignore the block..
+ }
+
+ } catch (EOFException ex) {
+ done = true;
+ }
+ }
+
+ if (alignments.size() == 0) {
+ throw new MissingBlockException("DATA or CHARACTERS block is missing");
+ }
+
+ return alignments;
+ }
+
+ /**
+ * importSequences.
+ */
+ public List<Sequence> importSequences() throws IOException, ImportException {
+ boolean done = false;
+
+ List<Taxon> taxonList = null;
+ List<Sequence> sequences = null;
+
+ while (!done) {
+ try {
+ NexusBlock block = findNextBlock();
+ if (block == NexusBlock.TAXA) {
+ taxonList = readTaxaBlock();
+ } else if (block == NexusBlock.CHARACTERS) {
+ if (taxonList == null) {
+ throw new MissingBlockException("TAXA block is missing");
+ }
+ sequences = readCharactersBlock(taxonList);
+ done = true;
+ } else if (block == NexusBlock.DATA) {
+ // A data block doesn't need a taxon block before it
+ // but if one exists then it will use it.
+ sequences = readDataBlock(taxonList);
+ done = true;
+ } else {
+ // Ignore the block..
+ }
+ } catch (EOFException ex) {
+ done = true;
+ }
+ }
+ if (sequences == null) {
+ throw new MissingBlockException("DATA or CHARACTERS block is missing");
+ }
+ return sequences;
+ }
+
+ // **************************************************************
+ // TreeImporter IMPLEMENTATION
+ // **************************************************************
+
+ private boolean isReadingTreesBlock = false;
+ private List<Taxon> treeTaxonList = null;
+ private Map<String, Taxon> translationList = Collections.emptyMap();
+ private Tree nextTree = null;
+ private String[] lastToken = new String[1];
+
+ /**
+ * return whether another tree is available.
+ */
+ public boolean hasTree() throws IOException, ImportException
+ {
+ if (!isReadingTreesBlock) {
+ isReadingTreesBlock = startReadingTrees();
+ translationList = readTranslationList(treeTaxonList, lastToken);
+ }
+
+ if (!isReadingTreesBlock) return false;
+
+ if (nextTree == null) {
+ nextTree = readNextTree(lastToken);
+ }
+
+ return (nextTree != null);
+ }
+
+
+ /**
+ * import the next tree.
+ * return the tree or null if no more trees are available
+ */
+ public Tree importNextTree() throws IOException, ImportException
+ {
+ // call hasTree to do the hard work...
+ if (!hasTree()) {
+ isReadingTreesBlock = false;
+ return null;
+ }
+
+ Tree tree = nextTree;
+ nextTree = null;
+
+ return tree;
+ }
+
+ public List<Tree> importTrees() throws IOException, ImportException {
+ isReadingTreesBlock = false;
+ if (!startReadingTrees()) {
+ throw new MissingBlockException("TREES block is missing");
+ }
+ return readTreesBlock(treeTaxonList);
+ }
+
+ public boolean startReadingTrees() throws IOException, ImportException
+ {
+ treeTaxonList = null;
+
+ while (true) {
+ try {
+ NexusBlock block = findNextBlock();
+ switch( block ) {
+ case TAXA: treeTaxonList = readTaxaBlock(); break;
+ case TREES: return true;
+ // Ignore the block..
+ default: break;
+ }
+ } catch (EOFException ex) {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ // **************************************************************
+ // DistanceMatrixImporter IMPLEMENTATION
+ // **************************************************************
+
+ /**
+ * importDistances.
+ */
+ public List<DistanceMatrix> importDistanceMatrices() throws IOException, ImportException {
+ boolean done = false;
+
+ List<Taxon> taxonList = null;
+ List<DistanceMatrix> distanceMatrices = new ArrayList<DistanceMatrix>();
+
+ while (!done) {
+ try {
+
+ NexusBlock block = findNextBlock();
+
+ if (block == NexusBlock.TAXA) {
+
+ taxonList = readTaxaBlock();
+
+ } else if (block == NexusBlock.DISTANCES) {
+
+ if (taxonList == null) {
+ throw new MissingBlockException("TAXA block is missing");
+ }
+
+ DistanceMatrix distanceMatrix = readDistancesBlock(taxonList);
+ distanceMatrices.add(distanceMatrix);
+
+ } else {
+ // Ignore the block..
+ }
+
+ } catch (EOFException ex) {
+ done = true;
+ }
+ }
+
+ // assert distanceMatrices != null;
+ // throw new MissingBlockException("DISTANCES block is missing");
+
+ return distanceMatrices;
+ }
+
+ // **************************************************************
+ // PRIVATE Methods
+ // **************************************************************
+
+ /**
+ * Finds the end of the current block.
+ */
+ private void findToken(String query, boolean ignoreCase) throws IOException
+ {
+ String token;
+ boolean found = false;
+
+ do {
+ token = helper.readToken();
+
+ if ( (ignoreCase && token.equalsIgnoreCase(query)) || token.equals(query) ) {
+ found = true;
+ }
+ } while (!found);
+ }
+
+ /**
+ * Finds the end of the current block.
+ */
+ public void findEndBlock() throws IOException
+ {
+ try {
+ String token;
+
+ do {
+ token = helper.readToken(";");
+ } while ( !token.equalsIgnoreCase("END") && !token.equalsIgnoreCase("ENDBLOCK") );
+ } catch (EOFException e) {
+ // Doesn't matter if the End is missing
+ }
+
+ nextBlock = NexusBlock.UNKNOWN;
+ }
+
+ /**
+ * Reads the header information for a 'DATA', 'CHARACTERS' or 'TAXA' block.
+ */
+ private void readDataBlockHeader(String tokenToLookFor, NexusBlock block) throws ImportException, IOException
+ {
+
+ boolean dim = false, ttl = false, fmt = false;
+ String token;
+
+ do {
+ token = helper.readToken();
+
+ if ( token.equalsIgnoreCase("TITLE") ) {
+ if (ttl) {
+ throw new ImportException.DuplicateFieldException("TITLE");
+ }
+
+ ttl = true;
+ } else if ( token.equalsIgnoreCase("DIMENSIONS") ) {
+
+ if (dim) {
+ throw new ImportException.DuplicateFieldException("DIMENSIONS");
+ }
+
+ boolean nchar = (block == NexusBlock.TAXA);
+ boolean ntax = (block == NexusBlock.CHARACTERS);
+
+ do {
+ String token2 = helper.readToken( "=;" );
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Unknown subcommand, '" + token2 + "', or missing '=' in DIMENSIONS command");
+ }
+
+ if ( token2.equalsIgnoreCase("NTAX") ) {
+
+ if (block == NexusBlock.CHARACTERS) {
+ throw new ImportException.BadFormatException("NTAX subcommand in CHARACTERS block");
+ }
+
+ taxonCount = helper.readInteger( ";" );
+ ntax = true;
+
+ } else if ( token2.equalsIgnoreCase("NCHAR") ) {
+
+ if (block == NexusBlock.TAXA) {
+ throw new ImportException.BadFormatException("NCHAR subcommand in TAXA block");
+ }
+
+ siteCount = helper.readInteger( ";" );
+ nchar = true;
+
+ } else {
+ throw new ImportException.BadFormatException("Unknown subcommand, '" + token2 + "', in DIMENSIONS command");
+ }
+
+ } while (helper.getLastDelimiter() != ';');
+
+ if (!ntax) {
+ throw new ImportException.BadFormatException("NTAX subcommand missing from DIMENSIONS command");
+ }
+ if (!nchar) {
+ throw new ImportException.BadFormatException("NCHAR subcommand missing from DIMENSIONS command");
+ }
+ dim = true;
+
+ } else if ( token.equalsIgnoreCase("FORMAT") ) {
+
+ if (fmt) {
+ throw new ImportException.DuplicateFieldException("FORMAT");
+ }
+
+ sequenceType = null;
+
+ do {
+ String token2 = helper.readToken("=;");
+
+ if (token2.equalsIgnoreCase("GAP")) {
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Expecting '=' after GAP subcommand in FORMAT command");
+ }
+
+ gapCharacters = helper.readToken(";");
+
+ } else if (token2.equalsIgnoreCase("MISSING")) {
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Expecting '=' after MISSING subcommand in FORMAT command");
+ }
+
+ missingCharacters = helper.readToken(";");
+
+ } else if (token2.equalsIgnoreCase("MATCHCHAR")) {
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Expecting '=' after MATCHCHAR subcommand in FORMAT command");
+ }
+
+ matchCharacters = helper.readToken(";");
+
+ } else if (token2.equalsIgnoreCase("DATATYPE")) {
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Expecting '=' after DATATYPE subcommand in FORMAT command");
+ }
+
+ String token3 = helper.readToken(";");
+ if (token3.equalsIgnoreCase("NUCLEOTIDE") ||
+ token3.equalsIgnoreCase("DNA") ||
+ token3.equalsIgnoreCase("RNA")) {
+
+ sequenceType = SequenceType.NUCLEOTIDE;
+
+ } else if (token3.equalsIgnoreCase("PROTEIN")) {
+
+ sequenceType = SequenceType.AMINO_ACID;
+
+ } else if (token3.equalsIgnoreCase("CONTINUOUS")) {
+
+ throw new ImportException.UnparsableDataException("Continuous data cannot be parsed at present");
+
+ }
+ } else if (token2.equalsIgnoreCase("INTERLEAVE")) {
+ isInterleaved = true;
+ }
+
+ } while (helper.getLastDelimiter() != ';');
+
+ fmt = true;
+ }
+ } while ( !token.equalsIgnoreCase(tokenToLookFor) );
+
+ if ( !dim ) {
+ throw new ImportException.MissingFieldException("DIMENSIONS");
+ }
+ if ( block != NexusBlock.TAXA && sequenceType == null ) {
+ throw new ImportException.MissingFieldException("DATATYPE. Only Nucleotide or Protein sequences are supported.");
+ }
+ }
+
+ /**
+ * Reads sequences in a 'DATA' or 'CHARACTERS' block.
+ */
+ private List<Sequence> readSequenceData(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ boolean sequencherStyle = false;
+ String firstSequence = null;
+ List<Sequence> sequences = new ArrayList<Sequence>();
+
+ if (isInterleaved) {
+ List<String> sequencesData = new ArrayList<String>(taxonCount);
+ List<Taxon> taxons = new ArrayList<Taxon>();
+ List<Taxon> taxList = (taxonList != null) ? taxonList : taxons;
+
+ int[] charsRead = new int[taxonCount];
+ for (int i = 0; i < taxonCount; i++) {
+ sequencesData.add("");
+ charsRead[i] = 0;
+ }
+ //throw new ImportException.UnparsableDataException("At present, interleaved data is not parsable");
+ boolean firstLoop = true;
+
+ int readCount = 0;
+ while (readCount < siteCount * taxonCount) {
+
+ for (int i = 0; i < taxonCount; i++) {
+
+ String token = helper.readToken();
+
+ int sequenceIndex;
+ Taxon taxon = Taxon.getTaxon(token);
+ if (firstLoop) {
+ if (taxonList != null ) {
+ sequenceIndex = taxonList.indexOf(taxon);
+ } else {
+ sequenceIndex = taxons.size();
+ taxons.add(taxon);
+ }
+ } else {
+ sequenceIndex = taxList.indexOf(taxon);
+ }
+
+ if( sequenceIndex < 0 ) {
+ // taxon not found in taxon list...
+ // ...perhaps it is a numerical taxon reference?
+ throw new ImportException.UnknownTaxonException("Unexpected taxon:" + token
+ + " (expecting " + taxList.get(i).getName() + ")");
+ }
+
+ StringBuffer buffer = new StringBuffer();
+
+ helper.readSequenceLine(buffer, sequenceType, ";", gapCharacters, missingCharacters,
+ matchCharacters, firstSequence);
+
+ String seqString = buffer.toString();
+
+ // We now check if this file is in Sequencher* style NEXUS, this style has the taxon and site counts
+ // before the sequence data.
+ try{
+ if(firstLoop && Integer.parseInt(taxon.toString()) == taxonCount &&
+ Integer.parseInt(seqString) == siteCount){
+ i--;
+ taxons.remove(taxon);
+ sequencherStyle = true;
+ continue;
+ }
+ } catch(NumberFormatException e) {
+ // Do nothing, this just means that this is the NEXUS format we usually expect rather than sequencher
+ }
+
+ readCount += seqString.length();
+ charsRead[sequenceIndex] += seqString.length();
+
+ sequencesData.set(sequenceIndex, sequencesData.get(sequenceIndex).concat(seqString));
+ if (i == 0) {
+ firstSequence = seqString;
+ }
+
+ if (helper.getLastDelimiter() == ';') {
+ if (i < taxonCount - 1) {
+ throw new ImportException.TooFewTaxaException();
+ }
+ for (int k = 0; k < taxonCount; k++) {
+ if (charsRead[k] != siteCount) {
+ throw new ImportException.ShortSequenceException(taxList.get(k).getName()
+ + " has length " + charsRead[k] + ", expecting " + siteCount);
+ }
+ }
+ }
+ }
+
+ firstLoop = false;
+ }
+
+ // Sequencher style apparently doesnt use a ';' after the sequence data.
+ if (!sequencherStyle && helper.getLastDelimiter() != ';') {
+ throw new ImportException.BadFormatException("Expecting ';' after sequences data");
+ }
+
+ for (int k = 0; k < taxonCount; k++) {
+ Sequence sequence = new BasicSequence(sequenceType, taxList.get(k), sequencesData.get(k));
+ sequences.add(sequence);
+ }
+
+ } else {
+
+ for (int i = 0; i < taxonCount; i++) {
+ String token = helper.readToken();
+
+ Taxon taxon = Taxon.getTaxon(token);
+
+ if (taxonList != null && !taxonList.contains(taxon)) {
+ // taxon not found in taxon list...
+ // ...perhaps it is a numerical taxon reference?
+ throw new ImportException.UnknownTaxonException(token);
+ }
+
+ StringBuilder buffer = new StringBuilder() ;
+ helper.readSequence(buffer, sequenceType, ";", siteCount, gapCharacters,
+ missingCharacters, matchCharacters, firstSequence, true);
+ String seqString = buffer.toString();
+
+ if (seqString.length() != siteCount) {
+ throw new ImportException.ShortSequenceException(taxon.getName()
+ + " has length " + seqString.length() + ", expecting " + siteCount);
+ }
+
+ if (i == 0) {
+ firstSequence = seqString;
+ }
+
+ if (helper.getLastDelimiter() == ';' && i < taxonCount - 1) {
+ throw new ImportException.TooFewTaxaException();
+ }
+
+ Sequence sequence = new BasicSequence(sequenceType, taxon, seqString);
+ sequences.add(sequence);
+ }
+
+ if (helper.getLastDelimiter() != ';') {
+ throw new ImportException.BadFormatException("Expecting ';' after sequences data");
+ }
+
+ }
+
+ return sequences;
+ }
+
+
+ /**
+ * Reads a 'TAXA' block.
+ */
+ private List<Taxon> readTaxaBlock() throws ImportException, IOException
+ {
+
+ taxonCount = 0;
+
+ readDataBlockHeader("TAXLABELS", NexusBlock.TAXA);
+
+ if (taxonCount == 0) {
+ throw new ImportException.MissingFieldException("NTAXA");
+ }
+
+ List<Taxon> taxa = new ArrayList<Taxon>();
+
+ do {
+ String name = helper.readToken(";");
+
+ Taxon taxon = Taxon.getTaxon(name);
+ taxa.add(taxon);
+
+ if (helper.getLastMetaComment() != null) {
+ // There was a meta-comment which should be in the form:
+ // \[&label[=value][,label[=value]>[,/..]]\]
+ parseMetaCommentPairs(helper.getLastMetaComment(), taxon);
+
+ helper.clearLastMetaComment();
+ }
+ } while (helper.getLastDelimiter() != ';');
+
+ if (taxa.size() != taxonCount) {
+ throw new ImportException.BadFormatException("Number of taxa doesn't match NTAXA field");
+ }
+
+ findEndBlock();
+
+ return taxa;
+ }
+
+ /**
+ * Reads a 'CHARACTERS' block.
+ */
+ private List<Sequence> readCharactersBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+
+ siteCount = 0;
+ sequenceType = null;
+
+ readDataBlockHeader("MATRIX", NexusBlock.CHARACTERS);
+
+ List<Sequence> sequences = readSequenceData(taxonList);
+
+ findEndBlock();
+
+ return sequences;
+ }
+
+ /**
+ * Reads a 'DATA' block.
+ */
+ private List<Sequence> readDataBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+
+ taxonCount = 0;
+ siteCount = 0;
+ sequenceType = null;
+
+ readDataBlockHeader("MATRIX", NexusBlock.DATA);
+
+ List<Sequence> sequences = readSequenceData(taxonList);
+
+ findEndBlock();
+
+ return sequences;
+ }
+
+ /**
+ * Reads a 'DISTANCES' block.
+ */
+ private DistanceMatrix readDistancesBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ if (taxonList == null) {
+ throw new ImportException.BadFormatException("Missing Taxa for reading distances");
+ }
+
+ Triangle triangle = Triangle.LOWER;
+ boolean diagonal = true;
+ boolean labels = false;
+
+ boolean ttl = false, fmt = false;
+
+ String token = helper.readToken();
+ while ( !token.equalsIgnoreCase("MATRIX") ) {
+
+ if ( token.equalsIgnoreCase("TITLE") ) {
+ if (ttl) {
+ throw new ImportException.DuplicateFieldException("TITLE");
+ }
+
+ ttl = true;
+ } else if ( token.equalsIgnoreCase("FORMAT") ) {
+
+ if (fmt) {
+ throw new ImportException.DuplicateFieldException("FORMAT");
+ }
+
+ sequenceType = null;
+
+ do {
+ String token2 = helper.readToken("=;");
+
+ if (token2.equalsIgnoreCase("TRIANGLE")) {
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Expecting '=' after TRIANGLE subcommand in FORMAT command");
+ }
+
+ String token3 = helper.readToken(";");
+ if (token3.equalsIgnoreCase("LOWER")) {
+ triangle = Triangle.LOWER;
+ } else if (token3.equalsIgnoreCase("UPPER")) {
+ triangle = Triangle.UPPER;
+ } else if (token3.equalsIgnoreCase("BOTH")) {
+ triangle = Triangle.BOTH;
+ }
+ } else
+ // based on the example in the PAUP manual
+ if (token2.equalsIgnoreCase("NODIAGONAL")) {
+ // in a valid file, triangle != both
+ diagonal = false;
+ } else if (token2.equalsIgnoreCase("LABELS")) {
+ labels = true;
+ }
+
+ } while (helper.getLastDelimiter() != ';');
+
+ fmt = true;
+ }
+ token = helper.readToken();
+ }
+
+ double[][] distances = new double[taxonList.size()][taxonList.size()];
+
+ for (int i = 0; i < taxonList.size(); i++) {
+ token = helper.readToken();
+
+ Taxon taxon = Taxon.getTaxon(token);
+
+ int index = taxonList.indexOf(taxon);
+
+ if (index < 0) {
+ // taxon not found in taxon list...
+ // ...perhaps it is a numerical taxon reference?
+ throw new ImportException.UnknownTaxonException(token);
+ }
+
+ if (index != i) {
+ throw new ImportException.BadFormatException("The taxon labels are in a different order to those in the TAXA block");
+ }
+
+ if (triangle == Triangle.LOWER) {
+ for (int j = 0; j < i + 1; j++) {
+ if (i != j) {
+ distances[i][j] = helper.readDouble();
+ distances[j][i] = distances[i][j];
+ } else {
+ if (diagonal) {
+ distances[i][j] = helper.readDouble();
+ }
+ }
+ }
+ } else if (triangle == Triangle.UPPER) {
+ for (int j = i; j < taxonList.size(); j++) {
+ if (i != j) {
+ distances[i][j] = helper.readDouble();
+ distances[j][i] = distances[i][j];
+ } else {
+ if (diagonal) {
+ distances[i][j] = helper.readDouble();
+ }
+ }
+ }
+ } else {
+ for (int j = 0; j < taxonList.size(); j++) {
+ if (i != j || diagonal) {
+ distances[i][j] = helper.readDouble();
+ } else {
+ distances[i][j] = 0.0;
+ }
+ }
+ }
+
+
+ if (helper.getLastDelimiter() == ';' && i < taxonList.size() - 1) {
+ throw new ImportException.TooFewTaxaException();
+ }
+ }
+
+ if (helper.nextCharacter() != ';') {
+ throw new ImportException.BadFormatException("Expecting ';' after sequences data");
+ }
+
+ findEndBlock();
+
+ return new BasicDistanceMatrix(taxonList, distances);
+ }
+
+
+ /**
+ * Reads a 'TREES' block.
+ */
+ private List<Tree> readTreesBlock(List<Taxon> taxonList) throws ImportException, IOException
+ {
+ List<Tree> trees = new ArrayList<Tree>();
+
+ String[] lastToken = new String[1];
+ translationList = readTranslationList(taxonList, lastToken);
+
+ while( true ) {
+
+ RootedTree tree = readNextTree(lastToken);
+
+ if (tree == null) {
+ break;
+ }
+
+ trees.add(tree);
+ }
+
+ if (trees.size() == 0) {
+ throw new ImportException.BadFormatException("No trees defined in TREES block");
+ }
+
+ nextBlock = NexusBlock.UNKNOWN;
+
+ return trees;
+ }
+
+ private Map<String, Taxon> readTranslationList(List<Taxon> taxonList, String[] lastToken) throws ImportException, IOException
+ {
+ Map<String, Taxon> translationList = new HashMap<String, Taxon>();
+
+ String token = helper.readToken(";");
+
+ if ( token.equalsIgnoreCase("TRANSLATE") ) {
+
+ do {
+ String token2 = helper.readToken(",;");
+
+ if (helper.getLastDelimiter() == ',' || helper.getLastDelimiter() == ';') {
+ if( token2.length() == 0 && (char)helper.getLastDelimiter() == ';') {
+ //assume an extra comma at end of list
+ break;
+
+ }
+ throw new ImportException.BadFormatException("Missing taxon label in TRANSLATE command of TREES block");
+ }
+
+ String token3 = helper.readToken(",;");
+
+ if (helper.getLastDelimiter() != ',' && helper.getLastDelimiter() != ';') {
+ throw new ImportException.BadFormatException("Expecting ',' or ';' after taxon label in TRANSLATE command of TREES block");
+ }
+
+ Taxon taxon = Taxon.getTaxon(token3);
+
+ if (taxonList != null && !taxonList.contains(taxon)) {
+ // taxon not found in taxon list...
+ // ...perhaps it is a numerical taxon reference?
+ throw new ImportException.UnknownTaxonException(token3);
+ }
+ translationList.put(token2, taxon);
+
+ } while (helper.getLastDelimiter() != ';');
+
+ token = helper.readToken(";");
+
+ } else if (taxonList != null) {
+ for (Taxon taxon : taxonList) {
+ translationList.put(taxon.getName(), taxon);
+ }
+ }
+
+ lastToken[0] = token;
+
+ return translationList;
+ }
+
+ private RootedTree readNextTree(String[] lastToken) throws ImportException, IOException
+ {
+ try {
+ SimpleRootedTree tree = null;
+ String token = lastToken[0];
+
+ boolean isUnrooted = token.equalsIgnoreCase("UTREE");
+ if ( isUnrooted || token.equalsIgnoreCase("TREE")) {
+
+ if (helper.nextCharacter() == '*') {
+ // Star is used to specify a default tree - ignore it
+ helper.readCharacter();
+ }
+
+ final String meta = helper.getLastMetaComment();
+ if (meta != null) {
+ // Look for the unrooted meta comment [&U]
+ if (meta.equalsIgnoreCase("U")) {
+ isUnrooted = true;
+ }
+ helper.clearLastMetaComment();
+ }
+
+ final String treeName = helper.readToken( "=;" );
+
+ if (helper.getLastDelimiter() != '=') {
+ throw new ImportException.BadFormatException("Missing label for tree'" + treeName + "' or missing '=' in TREE command of TREES block");
+ }
+
+ try {
+
+ if (helper.nextCharacter() != '(') {
+ throw new ImportException.BadFormatException("Missing tree definition in TREE command of TREES block");
+ }
+
+ // Save tree comment and attach it later
+ final String comment = helper.getLastMetaComment();
+ helper.clearLastMetaComment();
+
+ tree = new SimpleRootedTree();
+ readInternalNode(tree);
+
+ // save name as attribute
+ if( ! NexusExporter.isGeneratedTreeName(treeName) ) {
+ tree.setAttribute(NexusExporter.treeNameAttributeKey, treeName);
+ }
+
+ int last = helper.getLastDelimiter();
+ if( last == ':' ) {
+ // root length - discard for now
+ /*double rootLength = */ helper.readDouble(";");
+ last = helper.getLastDelimiter();
+ }
+
+ if (last != ';') {
+ throw new ImportException.BadFormatException("Expecting ';' after tree, '" + treeName + "', TREE command of TREES block");
+ }
+
+ if (comment != null) {
+ // if '[W number]' (MrBayes), set weight attribute
+ if( comment.matches("^W\\s+[\\+\\-]?[\\d\\.]+")) {
+ tree.setAttribute("weight", new Float(comment.substring(2)) );
+ } else {
+ try {
+ parseMetaCommentPairs(comment, tree);
+ } catch(ImportException.BadFormatException e) {
+ // set generic comment attribute
+ tree.setAttribute("comment", comment);
+ }
+ }
+ }
+
+ tree.setConceptuallyUnrooted(isUnrooted);
+
+ } catch (EOFException e) {
+ // If we reach EOF we may as well return what we have?
+ return tree;
+ }
+
+ token = helper.readToken(";");
+ } else if ( token.equalsIgnoreCase("ENDBLOCK") || token.equalsIgnoreCase("END") ) {
+ return null;
+ } else {
+ throw new ImportException.BadFormatException("Unknown command '" + token + "' in TREES block");
+ }
+
+ //added this to escape readNextTree loop correctly -- AJD
+ lastToken[0] = token;
+
+ return compactTrees ? new CompactRootedTree(tree) : tree;
+
+ } catch (EOFException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Reads a branch in. This could be a node or a tip (calls readNode or readTip
+ * accordingly). It then reads the branch length and SimpleNode that will
+ * point at the new node or tip.
+ */
+ private Node readBranch(SimpleRootedTree tree) throws IOException, ImportException
+ {
+ Node branch;
+
+ helper.clearLastMetaComment();
+ if (helper.nextCharacter() == '(') {
+ // is an internal node
+ branch = readInternalNode(tree);
+
+ } else {
+ // is an external node
+ branch = readExternalNode(tree);
+ }
+
+ if (helper.getLastDelimiter() == ':') {
+ final double length = helper.readDouble(",():;");
+ tree.setLength(branch, length);
+ }
+
+ // If there is a metacomment after the branch length indicator (:), then it is a branch attribute
+ // however, in the present implementation, this simply gets added to the node attributes.
+ if (helper.getLastMetaComment() != null) {
+ // There was a meta-comment which should be in the form:
+ // \[&label[=value][,label[=value]>[,/..]]\]
+ parseMetaCommentPairs(helper.getLastMetaComment(), branch);
+
+ helper.clearLastMetaComment();
+ }
+
+ return branch;
+ }
+
+ /**
+ * Reads a node in. This could be a polytomy. Calls readBranch on each branch
+ * in the node.
+ * @param tree
+ * @return
+ */
+ private Node readInternalNode(SimpleRootedTree tree) throws IOException, ImportException
+ {
+ List<Node> children = new ArrayList<Node>();
+
+ // read the opening '('
+ helper.readCharacter();
+
+ // read the first child
+ children.add( readBranch(tree) );
+
+ if (helper.getLastDelimiter() != ',') {
+ //throw new ImportException.BadFormatException("Missing ',' in tree");
+ }
+ // MK: previously, an internal node must have at least 2 children.
+ // MK: We we now allow trees with a single child so that we can create proper taxonomy
+ // MK: trees with only a single child at a taxonomy level.
+
+ // read subsequent children
+
+ while(helper.getLastDelimiter()==',') {
+ children.add( readBranch(tree) );
+ }
+
+ //System.out.println("kids="+children.size());
+ // should have had a closing ')'
+ if (helper.getLastDelimiter() != ')') {
+ throw new ImportException.BadFormatException("Missing closing ')' in tree");
+ }
+
+ Node node = tree.createInternalNode(children);
+
+ // find the next delimiter
+ String token = helper.readToken(":(),;").trim();
+
+ // if there is a token before the branch length, treat it as a node label
+ // and store it as an attribute of the node...
+ if (token.length() > 0) {
+ node.setAttribute("label", parseValue(token));
+ }
+
+ // If there is a metacomment before the branch length indicator (:), then it is a node attribute
+ if (helper.getLastMetaComment() != null) {
+ // There was a meta-comment which should be in the form:
+ // \[&label[=value][,label[=value]>[,/..]]\]
+ parseMetaCommentPairs(helper.getLastMetaComment(), node);
+
+ helper.clearLastMetaComment();
+ }
+
+ return node;
+ }
+
+ /**
+ * Reads an external node in.
+ */
+ private Node readExternalNode(SimpleRootedTree tree) throws ImportException, IOException
+ {
+ String label = helper.readToken(":(),;");
+
+ Taxon taxon;
+ try {
+ taxon = Taxon.getTaxon(label);
+ } catch (IllegalArgumentException e) {
+ throw new ImportException.UnknownTaxonException(e.getMessage());
+ }
+
+ if (translationList.size() > 0) {
+ taxon = translationList.get(label);
+
+ if (taxon == null) {
+ // taxon not found in taxon list...
+ throw new ImportException.UnknownTaxonException("Taxon in tree, '" + label + "' is unknown");
+ }
+ }
+
+ try {
+ final Node node = tree.createExternalNode(taxon);
+
+ // Attempt to parse external node attributes
+ // If there is a metacomment before the branch length indicator (:), then it is a node attribute
+ if (helper.getLastMetaComment() != null) {
+ // There was a meta-comment which should be in the form:
+ // \[&label[=value][,label[=value]>[,/..]]\]
+ parseMetaCommentPairs(helper.getLastMetaComment(), node);
+
+ helper.clearLastMetaComment();
+ }
+
+ return node;
+ } catch (IllegalArgumentException e) {
+ throw new ImportException.DuplicateTaxaException(e.getMessage());
+ }
+ }
+
+ static void parseMetaCommentPairs(String meta, Attributable item) throws ImportException.BadFormatException {
+ // This regex should match key=value pairs, separated by commas
+ // This can match the following types of meta comment pairs:
+ // value=number, value="string", value={item1, item2, item3}
+ // (label must be quoted if it contains spaces (i.e. "my label"=label)
+
+ Pattern pattern = Pattern.compile("(\"[^\"]*\"+|[^,=\\s]+)\\s*(=\\s*(\\{[^=}]*\\}|\"[^\"]*\"+|[^,]+))?");
+ Matcher matcher = pattern.matcher(meta);
+
+ while (matcher.find()) {
+ String label = matcher.group(1);
+ if( label.charAt(0) == '\"' ) {
+ label = label.substring(1, label.length() - 1);
+ }
+ if (label == null || label.trim().length() == 0) {
+ throw new ImportException.BadFormatException("Badly formatted attribute: '"+ matcher.group()+"'");
+ }
+ final String value = matcher.group(2);
+ if (value != null && value.trim().length() > 0) {
+ // there is a specified value so try to parse it
+ item.setAttribute(label, parseValue(value.substring(1)));
+ } else {
+ item.setAttribute(label, Boolean.TRUE);
+ }
+ }
+ }
+
+ /**
+ * This method takes a string and tries to decode it returning the object
+ * that best fits the data. It will recognize command delimited lists enclosed
+ * in {..} and call parseValue() on each element. It will also recognize Boolean,
+ * Integer and Double. If the value starts with a # then it will attempt to decode
+ * the following integer as an RGB colour - see Color.decode(). If nothing else fits
+ * then the value will be returned as a string but trimmed of leading and trailing
+ * white space.
+ * @param value the string
+ * @return the object
+ */
+ static Object parseValue(String value) {
+
+ value = value.trim();
+
+ if (value.startsWith("{")) {
+ // the value is a list so recursively parse the elements
+ // and return an array
+ String[] elements = value.substring(1, value.length() - 1).split(",");
+ Object[] values = new Object[elements.length];
+ for (int i = 0; i < elements.length; i++) {
+ values[i] = parseValue(elements[i]);
+ }
+ return values;
+ }
+
+ if (value.startsWith("#")) {
+ // I am not sure whether this is a good idea but
+ // I am going to assume that a # denotes an RGB colour
+ try {
+ return Color.decode(value.substring(1));
+ } catch (NumberFormatException nfe1) {
+ // not a colour
+ }
+ }
+
+ // A string qouted by the nexus exporter and such
+ if( value.startsWith("\"") && value.endsWith("\"") ) {
+ return value.subSequence(1, value.length() - 1);
+ }
+
+ if (value.equalsIgnoreCase("TRUE") || value.equalsIgnoreCase("FALSE")) {
+ return Boolean.valueOf(value);
+ }
+
+ // Attempt to format the value as an integer
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe1) {
+ // not an integer
+ }
+
+ // Attempt to format the value as a double
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException nfe2) {
+ // not a double
+ }
+
+ // return the trimmed string
+ return value;
+ }
+
+ // private stuff
+ private NexusBlock nextBlock = null;
+ private String nextBlockName = null;
+
+ private int taxonCount = 0, siteCount = 0;
+ private SequenceType sequenceType = null;
+ private String gapCharacters = "-";
+ private String matchCharacters = ".";
+ private String missingCharacters = "?";
+ private boolean isInterleaved = false;
+
+ protected final ImportHelper helper;
+}
diff --git a/src/jebl/evolution/io/PHYLIPExporter.java b/src/jebl/evolution/io/PHYLIPExporter.java
new file mode 100644
index 0000000..f0f34a6
--- /dev/null
+++ b/src/jebl/evolution/io/PHYLIPExporter.java
@@ -0,0 +1,117 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * Export alignment to Phylip format.
+ *
+ * Must be one of the most braindead format around. Try to output something that hopefuly any
+ * "Phylip supported" variant can read - up to 9 name chars, followed by a blank, followed by
+ * sequence on one line.
+ *
+ * @author Joseph Heled
+ * @version $Id: PHYLIPExporter.java 540 2006-11-23 19:59:23Z pepster $
+ */
+public class PHYLIPExporter implements AlignmentExporter, TreeExporter {
+ private PrintWriter writer;
+
+ /**
+ *
+ * @param writer where export text goes
+ */
+ public PHYLIPExporter(Writer writer) {
+ this.writer = new PrintWriter(writer);
+ }
+
+ private boolean namesUnique(List<String> names) {
+ Set<String> all = new HashSet<String>();
+ for( String name : names ) {
+ if( all.contains(name) ) {
+ return false;
+ }
+ all.add(name);
+ }
+ return true;
+ }
+
+ private List<String> tryNames(List<String> names, int fromBegining, int fromEnd) {
+ List<String> pnames = new ArrayList<String>(names.size());
+ final int total = fromBegining + fromEnd;
+ for( String name : names ) {
+ final int len = name.length();
+ String n;
+
+ if( len <= total ) {
+ n = name + ((len<total) ? String.format("%" + (total-len) + "s", " ") : "");
+ } else {
+ n = name.substring(0, fromBegining) + name.substring(len - fromEnd, len);
+ }
+ pnames.add(n);
+ }
+ if( namesUnique(pnames) ) {
+ return pnames;
+ }
+ return null;
+ }
+
+ private List<String> phylipNames(List<Sequence> seqs) {
+ List<String> names = new ArrayList<String>();
+ for( Sequence s : seqs ) {
+ // PHYML plugin does not like spaces in names. I guess this may mean Phylip does not allows them,
+ // but not sure (JH)
+ names.add(s.getTaxon().getName().replace(' ', '_') );
+ }
+
+ List<String> pnames = tryNames(names, 9, 0);
+ if( pnames == null ) {
+ pnames = tryNames(names, 0, 9);
+ }
+ if( pnames == null ) {
+ pnames = tryNames(names, 5, 4);
+ }
+
+ if( pnames == null ) {
+ final int nDig = (int)Math.ceil(Math.log10(names.size()));
+ pnames = new ArrayList<String>(names.size());
+ for(int i = 0; i < names.size(); ++i) {
+ String f = "%" + (9-nDig) + "." + (9-nDig) + "s%0" + nDig + "d";
+ pnames.add(String.format(f, names.get(i), i));
+ }
+ }
+ return pnames;
+ }
+
+ public void exportAlignment(Alignment alignment) throws IOException {
+ List<Sequence> seqs = alignment.getSequenceList();
+ writer.println(" " + seqs.size() + " " + seqs.get(0).getLength());
+ List<String> names = phylipNames(seqs);
+
+ for(int i = 0; i < seqs.size(); ++i) {
+ writer.print(names.get(i) + " ");
+ writer.println(seqs.get(i).getString());
+ }
+ }
+
+ // Should call those only after the alignment
+
+ public void exportTree(Tree tree) throws IOException {
+ final RootedTree rtree = Utils.rootTheTree(tree);
+ writer.print(Utils.toNewick(rtree));
+ writer.println(";");
+ }
+
+ public void exportTrees(Collection<? extends Tree> trees) throws IOException {
+ for( Tree t : trees ) {
+ exportTree(t);
+ }
+ }
+}
diff --git a/src/jebl/evolution/io/PhylipSequentialImporter.java b/src/jebl/evolution/io/PhylipSequentialImporter.java
new file mode 100644
index 0000000..73c3d1a
--- /dev/null
+++ b/src/jebl/evolution/io/PhylipSequentialImporter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005 Your Corporation. All Rights Reserved.
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.BasicSequence;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.taxa.Taxon;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for importing PHYLIP sequential file format
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: PhylipSequentialImporter.java 553 2006-12-04 21:46:56Z twobeers $
+ */
+public class PhylipSequentialImporter implements SequenceImporter {
+
+ /**
+ * Constructor
+ */
+ public PhylipSequentialImporter(Reader reader, SequenceType sequenceType, int maxNameLength) {
+ helper = new ImportHelper(reader);
+
+ this.sequenceType = sequenceType;
+ this.maxNameLength = maxNameLength;
+ }
+
+ /**
+ * importSequences.
+ */
+ public List<Sequence> importSequences() throws IOException, ImportException {
+
+ List<Sequence> sequences = new ArrayList<Sequence>();
+
+ try {
+
+ int taxonCount = helper.readInteger();
+ int siteCount = helper.readInteger();
+
+ String firstSeq = null;
+
+ for (int i = 0; i < taxonCount; i++) {
+ StringBuilder name = new StringBuilder();
+
+ char ch = helper.read();
+ int n = 0;
+ while (!Character.isWhitespace(ch) && (maxNameLength < 1 || n < maxNameLength)) {
+ name.append(ch);
+ ch = helper.read();
+ n++;
+ }
+
+ StringBuilder seq = new StringBuilder(siteCount);
+ helper.readSequence(seq, sequenceType, "", siteCount, "-", "?", ".", firstSeq);
+
+ if (firstSeq == null) {
+ firstSeq = seq.toString();
+ }
+ sequences.add(new BasicSequence(sequenceType, Taxon.getTaxon(name.toString()), seq.toString()));
+ }
+ } catch (EOFException e) {
+ }
+
+ return sequences;
+ }
+
+ private final ImportHelper helper;
+ private final SequenceType sequenceType;
+ private int maxNameLength = 10;
+}
diff --git a/src/jebl/evolution/io/SequenceExporter.java b/src/jebl/evolution/io/SequenceExporter.java
new file mode 100644
index 0000000..7ae686c
--- /dev/null
+++ b/src/jebl/evolution/io/SequenceExporter.java
@@ -0,0 +1,21 @@
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.Sequence;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: SequenceExporter.java 429 2006-08-26 18:17:39Z rambaut $
+ */
+public interface SequenceExporter {
+
+ /**
+ * exportSequences.
+ */
+ void exportSequences(Collection<? extends Sequence> sequences) throws IOException;
+}
diff --git a/src/jebl/evolution/io/SequenceImporter.java b/src/jebl/evolution/io/SequenceImporter.java
new file mode 100644
index 0000000..f08dfd7
--- /dev/null
+++ b/src/jebl/evolution/io/SequenceImporter.java
@@ -0,0 +1,31 @@
+/*
+ * SequenceImporter.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.sequences.Sequence;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Interface for importers that do sequences
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: SequenceImporter.java 442 2006-09-05 21:59:20Z matt_kearse $
+ */
+public interface SequenceImporter {
+
+ /**
+ * importSequences.
+ */
+ List<Sequence> importSequences() throws IOException, ImportException;
+}
diff --git a/src/jebl/evolution/io/TabDelimitedImporter.java b/src/jebl/evolution/io/TabDelimitedImporter.java
new file mode 100644
index 0000000..6890237
--- /dev/null
+++ b/src/jebl/evolution/io/TabDelimitedImporter.java
@@ -0,0 +1,164 @@
+package jebl.evolution.io;
+
+import jebl.evolution.distances.BasicDistanceMatrix;
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.taxa.Taxon;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: TabDelimitedImporter.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class TabDelimitedImporter implements DistanceMatrixImporter {
+
+ /**
+ * Constructor
+ */
+ public TabDelimitedImporter(Reader reader, Triangle triangle, boolean diagonal, boolean rowLabels, boolean columnLabels) {
+ helper = new ImportHelper(reader);
+ this.triangle = triangle;
+ this.diagonal = diagonal;
+ this.rowLabels = rowLabels;
+ this.columnLabels = columnLabels;
+
+ if (!rowLabels && !columnLabels) {
+ throw new IllegalArgumentException("The matrix must have either row labels or column labels (or both)");
+ }
+ }
+
+ /**
+ * importDistances.
+ */
+ public List<DistanceMatrix> importDistanceMatrices() throws IOException, ImportException {
+ List<Taxon> taxa = new ArrayList<Taxon>();
+ List<List<Double>> rows = new ArrayList<List<Double>>();
+
+ boolean done = false;
+
+ if (columnLabels) {
+ String line = helper.readLine();
+ String[] labels = line.split("\t");
+ for (String label : labels) {
+ Taxon taxon = Taxon.getTaxon(label);
+ if (taxa.contains(taxon)) {
+ throw new ImportException.BadFormatException("The taxon label, " + taxon.getName() + ", appears more than once in the matrix");
+ }
+
+ taxa.add(taxon);
+ }
+ }
+
+
+ do {
+ try {
+ helper.skipWhile(" ");
+
+ String line = helper.readLine();
+ String[] tokens = line.split("\t");
+
+ int i = 0;
+ if (rowLabels) {
+ Taxon taxon = Taxon.getTaxon(tokens[0]);
+ i++;
+
+ if (columnLabels) {
+ int index = taxa.indexOf(taxon);
+ if (index != i) {
+ throw new ImportException.BadFormatException("The row label, " + taxon.getName() + ", is missing or in a different order from the column labels");
+ }
+ } else {
+ if (taxa.contains(taxon)) {
+ throw new ImportException.BadFormatException("The taxon label, " + taxon.getName() + ", appears more than once in the matrix");
+ }
+
+ taxa.add(taxon);
+ }
+ }
+
+ List<Double> row = new ArrayList<Double>();
+ for (int j = i; j < tokens.length; j++) {
+ row.add(Double.parseDouble(tokens[j]));
+ }
+
+ rows.add(row);
+
+ } catch( EOFException eofe ) {
+ done = true;
+ }
+
+ } while (!done);
+
+ double[][] distances = new double[rows.size()][rows.size()];
+
+ int i = 0;
+ for (List<Double> row : rows) {
+
+ if (i >= distances.length) {
+ throw new ImportException.BadFormatException("Too many rows in matrix");
+ }
+ if (triangle == Triangle.LOWER) {
+ int j = 0;
+ for (Double distance : row) {
+ if (j >= distances[i].length) {
+ throw new ImportException.BadFormatException("Too many values in row " + Integer.toString(i+1) + " of matrix");
+ }
+ if (i != j) {
+ distances[i][j] = distance.doubleValue();
+ distances[j][i] = distance.doubleValue();
+ } else {
+ if (diagonal) {
+ distances[i][j] = distance.doubleValue();
+ }
+ }
+ j++;
+ }
+ } else if (triangle == Triangle.UPPER) {
+ int j = i;
+ for (Double distance : row) {
+ if (j >= distances[i].length) {
+ throw new ImportException.BadFormatException("Too many values in row " + Integer.toString(i+1) + " of matrix");
+ }
+ if (i != j) {
+ distances[i][j] = distance.doubleValue();
+ distances[j][i] = distance.doubleValue();
+ } else {
+ if (diagonal) {
+ distances[i][j] = helper.readDouble();
+ }
+ }
+ j++;
+ }
+ } else {
+ int j = 0;
+ for (Double distance : row) {
+ if (j >= distances[i].length) {
+ throw new ImportException.BadFormatException("Too many values in row " + Integer.toString(i+1) + " of matrix");
+ }
+ if (i != j || diagonal) {
+ distances[i][j] = distance.doubleValue();
+ } else {
+ distances[i][j] = 0.0;
+ }
+ j++;
+ }
+ }
+ i++;
+ }
+
+ List<DistanceMatrix> matrices = new ArrayList<DistanceMatrix>();
+ matrices.add(new BasicDistanceMatrix(taxa, distances));
+ return matrices;
+ }
+
+ private final ImportHelper helper;
+ private final Triangle triangle;
+ private final boolean diagonal;
+ private final boolean rowLabels;
+ private final boolean columnLabels;
+}
diff --git a/src/jebl/evolution/io/TreeExporter.java b/src/jebl/evolution/io/TreeExporter.java
new file mode 100644
index 0000000..b9c94b3
--- /dev/null
+++ b/src/jebl/evolution/io/TreeExporter.java
@@ -0,0 +1,30 @@
+package jebl.evolution.io;
+
+import jebl.evolution.alignments.Alignment;
+import jebl.evolution.trees.Tree;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: TreeExporter.java 429 2006-08-26 18:17:39Z rambaut $
+ */
+public interface TreeExporter {
+
+ /**
+ * Export a single tree
+ * @param tree
+ * @throws IOException
+ */
+ void exportTree(Tree tree) throws IOException;
+
+ /**
+ * Export a collection of trees
+ * @param trees
+ * @throws IOException
+ */
+ void exportTrees(Collection<? extends Tree> trees) throws IOException;
+}
diff --git a/src/jebl/evolution/io/TreeImporter.java b/src/jebl/evolution/io/TreeImporter.java
new file mode 100644
index 0000000..5d7d657
--- /dev/null
+++ b/src/jebl/evolution/io/TreeImporter.java
@@ -0,0 +1,55 @@
+/*
+ * TreeImporter.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.io;
+
+import jebl.evolution.trees.Tree;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Interface for importers that do trees
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: TreeImporter.java 301 2006-04-17 15:35:01Z rambaut $
+ */
+public interface TreeImporter extends Iterable<Tree> {
+
+ /**
+ * This can be used to read one tree at a time in a loop:
+ * <code>
+ * List<Tree> trees = new ArrayList<Tree>();
+ * while (hasTree()) {
+ * trees.add(importNextTree());
+ * }
+ * </code>
+ * return whether another tree is available.
+ */
+ boolean hasTree() throws IOException, ImportException;
+
+ /**
+ * Import a single tree
+ * @return the tree
+ * @throws IOException
+ * @throws ImportException
+ */
+ Tree importNextTree() throws IOException, ImportException;
+
+ /**
+ * Import all the trees
+ * @return the list of trees
+ * @throws IOException
+ * @throws ImportException
+ * Any type of tree is fine.
+ */
+ List<Tree> importTrees() throws IOException, ImportException;
+}
diff --git a/src/jebl/evolution/parsimony/FitchParsimony.java b/src/jebl/evolution/parsimony/FitchParsimony.java
new file mode 100644
index 0000000..9efe32a
--- /dev/null
+++ b/src/jebl/evolution/parsimony/FitchParsimony.java
@@ -0,0 +1,338 @@
+/*
+ * FitchParsimony.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.parsimony;
+
+import jebl.evolution.alignments.Pattern;
+import jebl.evolution.alignments.Patterns;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for reconstructing characters using Fitch parsimony. This is intended to be much faster
+ * than the static methods in the utility "Parsimony" class.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: FitchParsimony.java 604 2007-01-04 20:22:42Z msuchard $
+ */
+public class FitchParsimony implements ParsimonyCriterion {
+
+ private final SequenceType sequenceType;
+ private final int stateCount;
+ private final boolean gapsAreStates;
+
+ private Map<Node, boolean[][]> stateSets = new HashMap<Node, boolean[][]>();
+ private Map<Node, State[]> states = new HashMap<Node, State[]>();
+
+// private boolean[][] union; // Must now be local to recursive function
+// private boolean[][] intersection; // as nodes are not guaranteed to be called in post-order
+
+ private RootedTree tree = null;
+ private final List<Pattern> patterns;
+ private List<Taxon> taxa;
+
+ private boolean hasCalculatedSteps = false;
+ private boolean hasRecontructedStates = false;
+
+ private final double[] siteScores;
+
+ public FitchParsimony(List<Pattern> patterns, boolean gapsAreStates) {
+ if (patterns == null || patterns.size() == 0) {
+ throw new IllegalArgumentException("The patterns cannot be null or empty");
+ }
+
+ this.sequenceType = patterns.get(0).getSequenceType();
+ this.gapsAreStates = gapsAreStates;
+ this.taxa = patterns.get(0).getTaxa();
+
+ if (gapsAreStates) {
+ stateCount = sequenceType.getCanonicalStateCount() + 1;
+ } else {
+ stateCount = sequenceType.getCanonicalStateCount();
+
+ }
+
+ this.patterns = patterns;
+
+ this.siteScores = new double[patterns.size()];
+ }
+
+ public FitchParsimony(Patterns patterns, boolean gapsAreStates) {
+ this(patterns.getPatterns(), gapsAreStates);
+ }
+
+ /**
+ * Calculates the minimum number of siteScores for the parsimony reconstruction of a
+ * a set of character patterns on a tree. This only does the first pass of the
+ * Fitch algorithm so it does not store ancestral state reconstructions.
+ *
+ * @param tree a tree object to reconstruct the characters on
+ * @return number of parsimony siteScores
+ */
+ public double[] getSiteScores(Tree tree) {
+
+ if (tree == null) {
+ throw new IllegalArgumentException("The tree cannot be null");
+ }
+
+ if (!(tree instanceof RootedTree)) {
+ throw new IllegalArgumentException("The tree must be an instance of rooted tree");
+ }
+
+ if (this.tree == null || this.tree != tree) {
+ this.tree = (RootedTree) tree;
+
+ if (!Utils.isBinary(this.tree)) {
+ throw new IllegalArgumentException("The Fitch algorithm can only reconstruct ancestral states on binary trees");
+ }
+
+ initialize();
+ }
+
+ if (!hasCalculatedSteps) {
+ for (int i = 0; i < siteScores.length; i++) {
+ siteScores[i] = 0;
+ }
+ calculateSteps(this.tree); //this.tree.getRootNode());
+ hasCalculatedSteps = true;
+ }
+
+
+ return siteScores;
+ }
+
+ public double getScore(Tree tree) {
+
+ getSiteScores(tree);
+
+ double score = 0;
+
+ int i = 0;
+ for (Pattern pattern : patterns) {
+ score += siteScores[i] * pattern.getWeight();
+ i++;
+ }
+ return score;
+ }
+
+ /**
+ * Returns the reconstructed character states for a given node in the tree. If this method is repeatedly
+ * called with the same tree and patterns then only the first call will reconstruct the states and each
+ * subsequent call will return the stored states.
+ *
+ * @param tree a tree object to reconstruct the characters on
+ * @param node the node of the tree
+ * @return an array containing the reconstructed states for this node
+ */
+ public State[] getStates(Tree tree, Node node) {
+
+ getSiteScores(tree);
+
+ if (!hasRecontructedStates) {
+ reconstructStates(this.tree.getRootNode(), null);
+ hasRecontructedStates = true;
+ }
+
+ return states.get(node);
+ }
+
+ private void initialize() {
+ hasCalculatedSteps = false;
+ hasRecontructedStates = false;
+
+ for (Node node : tree.getNodes()) {
+ boolean[][] stateSet = new boolean[patterns.size()][stateCount];
+ stateSets.put(node, stateSet);
+
+ State[] stateArray = new State[patterns.size()];
+ states.put(node, stateArray);
+ }
+ }
+
+ /**
+ * This is the first pass of the Fitch algorithm. This calculates the set of states
+ * at each node and counts the total number of siteScores (the score). If that is all that
+ * is required then the second pass is not necessary.
+ */
+ private void calculateSteps(RootedTree tree) {
+
+ // nodes in pre-order
+ final List<Node> nodes = Utils.getNodes(tree, tree.getRootNode());
+
+ // used as locals in the loop below, allocated once
+ boolean[][] union = new boolean[patterns.size()][stateCount];
+ boolean[][] intersection = new boolean[patterns.size()][stateCount];
+
+ // iterate in reverse - post order. State of child is gurantted to be reasy before parent
+
+ for (int k = nodes.size() - 1; k >= 0; --k) {
+ final Node node = nodes.get(k);
+ final boolean[][] nodeStateSet = stateSets.get(node);
+
+ if (tree.isExternal(node)) {
+ boolean[][] stateSet = stateSets.get(node);
+ State[] stateArray = states.get(node);
+
+ for (int i = 0; i < patterns.size(); ++i) {
+ Pattern pattern = patterns.get(i);
+
+ Taxon taxon = tree.getTaxon(node);
+ int index = taxa.indexOf(taxon);
+
+ if (index == -1)
+ throw new IllegalArgumentException("Unknown taxon, " + taxon.getName() + " in tree");
+
+ State state = pattern.getState(index);
+ stateArray[i] = state;
+ if (gapsAreStates && state.isGap()) {
+ stateSet[i][stateCount - 1] = true;
+ } else {
+ for (State canonicalState : state.getCanonicalStates()) {
+ stateSet[i][canonicalState.getIndex()] = true;
+ }
+ }
+ }
+ } else {
+ boolean first = true;
+ for (Node child : tree.getChildren(node)) {
+ boolean[][] childStateSet = stateSets.get(child);
+ if (first) {
+ for (int i = 0; i < patterns.size(); i++) {
+ copyOf(childStateSet[i], union[i]);
+ copyOf(childStateSet[i], intersection[i]);
+ }
+ first = false;
+ } else {
+ for (int i = 0; i < patterns.size(); i++) {
+ unionOf(union[i], childStateSet[i], union[i]);
+ intersectionOf(intersection[i], childStateSet[i], intersection[i]);
+ }
+ }
+ }
+
+ for (int i = 0; i < patterns.size(); i++) {
+ if (sizeOf(intersection[i]) > 0) {
+ copyOf(intersection[i], nodeStateSet[i]);
+ } else {
+ copyOf(union[i], nodeStateSet[i]);
+ siteScores[i]++;
+ }
+ }
+ }
+ }
+ }
+
+
+ private String printState(boolean[][] stateSet) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0, n = stateSet.length; i < n; i++) {
+ sb.append("site " + i);
+ for (int j = 0, l = stateSet[i].length; j < l; j++) {
+ sb.append(" " + (stateSet[i][j] ? "T" : "F"));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+
+ private String printState(boolean[] stateSet) {
+ StringBuffer sb = new StringBuffer();
+// for(int i=0,n=stateSet.length; i<n; i++) {
+// int i = 0;
+// sb.append("site "+i);
+ for (int j = 0, l = stateSet.length; j < l; j++) {
+ sb.append(" " + (stateSet[j] ? "T" : "F"));
+ }
+// sb.append("\n");
+// }
+ return sb.toString();
+ }
+
+
+ /**
+ * The second pass of the Fitch algorithm. This reconstructs the ancestral states at
+ * each node.
+ *
+ * @param node
+ * @param parentStates
+ */
+ private void reconstructStates(Node node, State[] parentStates) {
+
+ if (!tree.isExternal(node)) {
+ boolean[][] nodeStateSet = stateSets.get(node);
+ State[] nodeStates = states.get(node);
+
+ for (int i = 0; i < patterns.size(); i++) {
+
+ if (parentStates != null && nodeStateSet[i][parentStates[i].getIndex()]) {
+ nodeStates[i] = parentStates[i];
+ } else {
+ int first = firstIndexOf(nodeStateSet[i]);
+ nodeStates[i] = sequenceType.getState(first);
+ }
+ }
+
+ for (Node child : tree.getChildren(node)) {
+ reconstructStates(child, nodeStates);
+ }
+ }
+ }
+
+ private static void copyOf(boolean[] s, boolean[] d) {
+
+ for (int i = 0; i < d.length; i++) {
+ d[i] = s[i];
+ }
+ }
+
+ private static void unionOf(boolean[] s1, boolean[] s2, boolean[] d) {
+
+ for (int i = 0; i < d.length; i++) {
+ d[i] = s1[i] || s2[i];
+ }
+ }
+
+ private static void intersectionOf(boolean[] s1, boolean[] s2, boolean[] d) {
+
+ for (int i = 0; i < d.length; i++) {
+ d[i] = s1[i] && s2[i];
+ }
+ }
+
+ private static int firstIndexOf(boolean[] s1) {
+
+ for (int i = 0; i < s1.length; i++) {
+ if (s1[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static int sizeOf(boolean[] s1) {
+
+ int count = 0;
+ for (int i = 0; i < s1.length; i++) {
+ if (s1[i]) count += 1;
+ }
+ return count;
+ }
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/parsimony/ParsimonyCriterion.java b/src/jebl/evolution/parsimony/ParsimonyCriterion.java
new file mode 100644
index 0000000..b57f361
--- /dev/null
+++ b/src/jebl/evolution/parsimony/ParsimonyCriterion.java
@@ -0,0 +1,51 @@
+/*
+ * ParsimonyCriterion.java
+ *
+ * (c) 2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.parsimony;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.sequences.State;
+import jebl.evolution.trees.Tree;
+
+/**
+ * @author rambaut
+ * @author Alexei Drummond
+ *
+ * Date: Jun 20, 2005
+ * Time: 4:56:34 PM
+ *
+ * @version $Id: ParsimonyCriterion.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public interface ParsimonyCriterion {
+
+ /**
+ * Calculates the minimum number of steps for the parsimony reconstruction for the given tree.
+ * It is expected that the implementation's constructor will be set up with the characters so
+ * that repeated calls can be made to this function to evaluate different trees.
+ * @param tree a tree object to reconstruct the characters on
+ * @return an array containing the parsimony score for each site
+ */
+ double[] getSiteScores(Tree tree);
+
+ /**
+ * Calculates the minimum number of steps for the parsimony reconstruction for the given tree.
+ * It is expected that the implementation's constructor will be set up with the characters so
+ * that repeated calls can be made to this function to evaluate different trees.
+ * @param tree a tree object to reconstruct the characters on
+ * @return the total score
+ */
+ double getScore(Tree tree);
+
+ /**
+ * Returns the reconstructed character states for a given node in the tree.
+ * @param tree a tree object to reconstruct the characters on
+ * @param node the node of the tree
+ * @return an array containing the reconstructed states for this node
+ */
+ State[] getStates(Tree tree, Node node);
+}
diff --git a/src/jebl/evolution/sequences/AminoAcidState.java b/src/jebl/evolution/sequences/AminoAcidState.java
new file mode 100644
index 0000000..08742f9
--- /dev/null
+++ b/src/jebl/evolution/sequences/AminoAcidState.java
@@ -0,0 +1,53 @@
+/*
+ * AminoAcidState.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: AminoAcidState.java 267 2006-03-21 02:36:55Z twobeers $
+ */
+public final class AminoAcidState extends State {
+
+ AminoAcidState(String name, String stateCode, int index) {
+ super(name, stateCode, index);
+ }
+
+ AminoAcidState(String name, String stateCode, int index, AminoAcidState[] ambiguities) {
+ super(name, stateCode, index, ambiguities);
+ }
+
+ public int compareTo(Object o) {
+ // throws ClassCastException on across-class comparison
+ AminoAcidState that = (AminoAcidState) o;
+ return super.compareTo(that);
+ }
+
+ // we do not need to override equals because there is only one
+ // unique instance of each nucleotide state - i.e. we can use ==
+ /*
+ public boolean equals(Object o) {
+ if (!(o instanceof AminoAcidState))
+ return false;
+ return super.equals(o);
+ }*/
+
+ public int hashCode() {
+ return 7 * super.hashCode() + 23;
+ }
+
+ public boolean isGap() {
+ return this == AminoAcids.GAP_STATE;
+ }
+
+ public boolean isStop() {
+ return this == AminoAcids.STOP_STATE;
+ }
+}
diff --git a/src/jebl/evolution/sequences/AminoAcids.java b/src/jebl/evolution/sequences/AminoAcids.java
new file mode 100644
index 0000000..805c017
--- /dev/null
+++ b/src/jebl/evolution/sequences/AminoAcids.java
@@ -0,0 +1,204 @@
+/*
+ * AminoAcids.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: AminoAcids.java 602 2006-12-29 01:18:11Z twobeers $
+ */
+public final class AminoAcids {
+ public static final String NAME = "amino acid";
+
+ public static final int CANONICAL_STATE_COUNT = 20;
+ public static final int STATE_COUNT = 26;
+
+ public static final AminoAcidState A_STATE = new AminoAcidState("A", "A", 0);
+ public static final AminoAcidState C_STATE = new AminoAcidState("C", "C", 1);
+ public static final AminoAcidState D_STATE = new AminoAcidState("D", "D", 2);
+ public static final AminoAcidState E_STATE = new AminoAcidState("E", "E", 3);
+ public static final AminoAcidState F_STATE = new AminoAcidState("F", "F", 4);
+ public static final AminoAcidState G_STATE = new AminoAcidState("G", "G", 5);
+ public static final AminoAcidState H_STATE = new AminoAcidState("H", "H", 6);
+ public static final AminoAcidState I_STATE = new AminoAcidState("I", "I", 7);
+ public static final AminoAcidState K_STATE = new AminoAcidState("K", "K", 8);
+ public static final AminoAcidState L_STATE = new AminoAcidState("L", "L", 9);
+ public static final AminoAcidState M_STATE = new AminoAcidState("M", "M", 10);
+ public static final AminoAcidState N_STATE = new AminoAcidState("N", "N", 11);
+ public static final AminoAcidState P_STATE = new AminoAcidState("P", "P", 12);
+ public static final AminoAcidState Q_STATE = new AminoAcidState("Q", "Q", 13);
+ public static final AminoAcidState R_STATE = new AminoAcidState("R", "R", 14);
+ public static final AminoAcidState S_STATE = new AminoAcidState("S", "S", 15);
+ public static final AminoAcidState T_STATE = new AminoAcidState("T", "T", 16);
+ public static final AminoAcidState V_STATE = new AminoAcidState("V", "V", 17);
+ public static final AminoAcidState W_STATE = new AminoAcidState("W", "W", 18);
+ public static final AminoAcidState Y_STATE = new AminoAcidState("Y", "Y", 19);
+ // If you use these, make sure to change the numbers of B..- below to 22..27,
+ // and increase CANONICAL_STATE_COUNT to 20, STATE_COUNT to 28
+ //public static final AminoAcidState U_STATE = new AminoAcidState("U", "U", 20); // Selenocysteine
+ // As of 2006-12-14 I think there is no IUPAC one letter code for Pyrrolysine, but BioJava uses "O"
+ //public static final AminoAcidState O_STATE = new AminoAcidState("O", "O", 21); // Pyrrolysine
+
+ public static final AminoAcidState[] CANONICAL_STATES = new AminoAcidState[]{
+ A_STATE, C_STATE, D_STATE, E_STATE, F_STATE,
+ G_STATE, H_STATE, I_STATE, K_STATE, L_STATE,
+ M_STATE, N_STATE, P_STATE, Q_STATE, R_STATE,
+ S_STATE, T_STATE, V_STATE, W_STATE, Y_STATE,
+ // U_STATE, O_STATE,
+ };
+
+ public static final AminoAcidState B_STATE = new AminoAcidState("B", "B", 20, new AminoAcidState[]{D_STATE, N_STATE});
+ public static final AminoAcidState Z_STATE = new AminoAcidState("Z", "Z", 21, new AminoAcidState[]{E_STATE, Q_STATE});
+ public static final AminoAcidState X_STATE = new AminoAcidState("X", "X", 22, CANONICAL_STATES);
+ public static final AminoAcidState UNKNOWN_STATE = new AminoAcidState("?", "?", 23, CANONICAL_STATES);
+ public static final AminoAcidState STOP_STATE = new AminoAcidState("*", "*", 24, CANONICAL_STATES);
+ public static final AminoAcidState GAP_STATE = new AminoAcidState("-", "-", 25, CANONICAL_STATES);
+
+ public static final AminoAcidState[] STATES = new AminoAcidState[]{
+ A_STATE, C_STATE, D_STATE, E_STATE, F_STATE,
+ G_STATE, H_STATE, I_STATE, K_STATE, L_STATE,
+ M_STATE, N_STATE, P_STATE, Q_STATE, R_STATE,
+ S_STATE, T_STATE, V_STATE, W_STATE, Y_STATE,
+ //U_STATE, O_STATE,
+ B_STATE, Z_STATE, X_STATE, UNKNOWN_STATE,
+ STOP_STATE, GAP_STATE
+ };
+
+ // Chemical classifications
+ public static final StateClassification CHEMICAL_CLASSIFICATION = new StateClassification.Default("chemical",
+ new String[]{"alphatic", "phenylalanine", "sulphur", "glycine", "hydroxyl", "tryptophan", "tyrosine", "proline", "acidic", "amide", "basic"},
+ new State[][]{{A_STATE, V_STATE, I_STATE, L_STATE}, {F_STATE}, {C_STATE, M_STATE}, {G_STATE}, {S_STATE, T_STATE},
+ {W_STATE}, {Y_STATE}, {P_STATE}, {D_STATE, E_STATE}, {N_STATE, Q_STATE}, {H_STATE, K_STATE, R_STATE}});
+
+ // Hydropathy classifications
+ public static final StateClassification HYDROPATHY_CLASSIFICATION = new StateClassification.Default("hydropathy",
+ // should the first entry be "hydrophobic" instead of "hydropathic" ?
+ new String[]{"hydropathic", "neutral", "hydrophilic"},
+ new State[][]{{I_STATE, V_STATE, L_STATE, F_STATE, C_STATE, M_STATE, A_STATE, W_STATE},
+ {G_STATE, T_STATE, S_STATE, Y_STATE, P_STATE, H_STATE},
+ {D_STATE, E_STATE, K_STATE, N_STATE, Q_STATE, R_STATE}});
+
+ // TT: I think the unit used here may be Angstrom^3, but I'm not sure; see http://www.imb-jena.de/IMAGE_AA.html
+ public static final StateClassification VOLUME_CLASSIFICATION = new StateClassification.Default("volume",
+ new String[]{"60-90", "108-117", "138-154", "162-174", "189-228"},
+ new State[][]{{G_STATE, A_STATE, S_STATE}, {C_STATE, D_STATE, P_STATE, N_STATE, T_STATE},
+ {E_STATE, V_STATE, Q_STATE, H_STATE}, {M_STATE, I_STATE, L_STATE, K_STATE, R_STATE},
+ {F_STATE, Y_STATE, W_STATE}});
+
+ /**
+ * This character represents the amino acid equivalent of a stop codon to cater for
+ * situations arising from converting coding DNA to an amino acid sequences.
+ */
+ /**
+ * A table to map state numbers (0-27) to their three letter codes.
+ */
+ private static final String[] AMINOACID_TRIPLETS = {
+ // A C D E F G H I K
+ "Ala", "Cys", "Asp", "Glu", "Phe", "Gly", "His", "Ile", "Lys",
+ // L M N P Q R S T V
+ "Leu", "Met", "Asn", "Pro", "Gln", "Arg", "Ser", "Thr", "Val",
+ //W Y B
+ "Trp", "Tyr", "Asx",
+
+ // U O
+ //"Sec", "Pyl",
+
+ // Z X * ? -
+ "Glx", " X ", " * ", " ? ", " - "
+ };
+ private static final int STATE_BY_CODE_SIZE = 128;
+
+ public static int getStateCount() {
+ return STATE_COUNT;
+ }
+
+ //public static List<State> getStates() { return Collections.unmodifiableList(Arrays.asList((State[])STATES)); }
+ public static List<AminoAcidState> getStates() {
+ return Collections.unmodifiableList(Arrays.asList(STATES));
+ }
+
+ public static int getCanonicalStateCount() {
+ return CANONICAL_STATE_COUNT;
+ }
+
+ public static List<State> getCanonicalStates() {
+ return Collections.unmodifiableList(Arrays.asList((State[]) CANONICAL_STATES));
+ }
+
+ public static AminoAcidState getState(char code) {
+ if (code < 0 || code >= STATE_BY_CODE_SIZE) {
+ return null;
+ }
+ return statesByCode[code];
+ }
+
+ public static AminoAcidState getState(String code) {
+ return getState(code.charAt(0));
+ }
+
+ public static AminoAcidState getState(int index) {
+ return STATES[index];
+ }
+
+ public static AminoAcidState getUnknownState() {
+ return UNKNOWN_STATE;
+ }
+
+ public static AminoAcidState getGapState() {
+ return GAP_STATE;
+ }
+
+ public static boolean isUnknown(AminoAcidState state) {
+ return state == UNKNOWN_STATE;
+ }
+
+ public static boolean isGap(AminoAcidState state) {
+ return state == GAP_STATE;
+ }
+
+ public static String getTripletCode(AminoAcidState state) {
+ return AMINOACID_TRIPLETS[state.getIndex()];
+ }
+
+ public static AminoAcidState[] toStateArray(String sequenceString) {
+ AminoAcidState[] seq = new AminoAcidState[sequenceString.length()];
+ for (int i = 0; i < seq.length; i++) {
+ seq[i] = getState(sequenceString.charAt(i));
+ }
+ return seq;
+ }
+
+ public static AminoAcidState[] toStateArray(byte[] indexArray) {
+ AminoAcidState[] seq = new AminoAcidState[indexArray.length];
+ for (int i = 0; i < seq.length; i++) {
+ seq[i] = getState(indexArray[i]);
+ }
+ return seq;
+ }
+
+ private static final AminoAcidState[] statesByCode;
+
+ static {
+ statesByCode = new AminoAcidState[STATE_BY_CODE_SIZE];
+ for (int i = 0; i < statesByCode.length; i++) {
+ // Undefined characters are mapped to null
+ statesByCode[i] = null;
+ }
+
+ for (AminoAcidState state : STATES) {
+ final char code = state.getCode().charAt(0);
+ statesByCode[code] = state;
+ statesByCode[Character.toLowerCase(code)] = state;
+ }
+ }
+}
diff --git a/src/jebl/evolution/sequences/BasicSequence.java b/src/jebl/evolution/sequences/BasicSequence.java
new file mode 100644
index 0000000..61a8f2e
--- /dev/null
+++ b/src/jebl/evolution/sequences/BasicSequence.java
@@ -0,0 +1,218 @@
+/*
+ * BasicSequence.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.util.AttributableHelper;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A default implementation of the Sequence interface.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: BasicSequence.java 673 2007-03-28 04:35:52Z matt_kearse $
+ */
+public class BasicSequence implements Sequence {
+
+ /**
+ * Creates a sequence with a name corresponding to the taxon name.
+ *
+ * Use CharSequence so both a String and a StringBuilder are fine
+ *
+ * @param taxon
+ * @param sequenceString
+ */
+
+ public BasicSequence(SequenceType sequenceType, Taxon taxon, CharSequence sequenceString) {
+
+ if (sequenceType == null) {
+ throw new IllegalArgumentException("sequenceType is not allowed to be null");
+ }
+ if (taxon == null) {
+ throw new IllegalArgumentException("taxon is not allowed to be null");
+ }
+
+ this.sequenceType = sequenceType;
+ this.taxon = taxon;
+ final int len = sequenceString.length();
+ this.sequenceCharacters = new byte[len];
+
+ for (int i = 0; i < len; i++) {
+ char c = sequenceString.charAt(i);
+ State state = sequenceType.getState(c);
+
+ if (state == null) {
+ // Something is wrong. Keep original length by inserting an unknown state
+ sequenceCharacters[i] ='?';
+ }
+ else {
+ sequenceCharacters[i] = (byte)c;
+ }
+ }
+ }
+
+ /**
+ * Creates a sequence with a name corresponding to the taxon name
+ *
+ * @param taxon
+ * @param sequenceType
+ * @param states
+ */
+ public BasicSequence(SequenceType sequenceType, Taxon taxon, State[] states) {
+
+ this.sequenceType = sequenceType;
+ this.taxon = taxon;
+ this.sequenceCharacters = new byte[states.length];
+ for (int i = 0; i < sequenceCharacters.length; i++) {
+ sequenceCharacters[i] = (byte)states[i].getCode().charAt(0);
+ }
+ }
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ public SequenceType getSequenceType() {
+ return sequenceType;
+ }
+
+ /**
+ * @return a string representing the sequence of symbols.
+ */
+ public String getString() {
+ StringBuilder buffer = new StringBuilder(sequenceCharacters.length);
+ for (int i : sequenceCharacters) {
+ buffer.append((char) i);
+ }
+ return buffer.toString();
+ }
+
+ public String getCleanString() {
+ StringBuilder buffer = new StringBuilder(sequenceCharacters.length);
+ for (int i : sequenceCharacters) {
+ State state = sequenceType.getState((char)i);
+ if (state.isAmbiguous() || state.isGap()) continue;
+ buffer.append(sequenceType.getState(i).getCode());
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * @return an array of state objects.
+ */
+ public State[] getStates() {
+ return sequenceType.toStateArray(getStateIndices());
+ }
+
+ public byte[] getStateIndices() {
+ byte results[]=new byte[sequenceCharacters.length];
+ for (int i = 0; i < sequenceCharacters.length; i++) {
+ results [i] = (byte) getState(i).getIndex();
+ }
+ return results;
+ }
+
+
+ /**
+ * Get the sequence characters representing the sequence.
+ * This return is a byte[] rather than a char[]
+ * to avoid using twice as much memory as necessary.
+ * The individual elements of the returned array can be cast to chars.
+ * @return the sequence characters as an array of characters.
+ */
+ public byte[] getSequenceCharacters() {
+ return sequenceCharacters;
+ }
+
+ /**
+ * @return the state at site.
+ */
+ public State getState(int site) {
+ return sequenceType.getState((char)sequenceCharacters[site]);
+ }
+
+ /**
+ * Returns the length of the sequence
+ *
+ * @return the length
+ */
+ public int getLength() {
+ return sequenceCharacters.length;
+ }
+
+ /**
+ * @return that taxon that this sequence represents (primarily used to match sequences with tree nodes)
+ */
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ /**
+ * Sequences are compared by their taxa
+ *
+ * @param o another sequence
+ * @return an integer
+ */
+ public int compareTo(Object o) {
+ return taxon.compareTo(((Sequence) o).getTaxon());
+ }
+
+ public String toString() {
+ return getString();
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if (helper != null) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ private AttributableHelper helper = null;
+
+ // private members
+
+ private final Taxon taxon;
+ private final SequenceType sequenceType;
+ private final byte[] sequenceCharacters; // this is really an array of characters, but using bytes since we don't store high-ascii characters
+
+ // private Map<String, Object> attributeMap = null;
+}
diff --git a/src/jebl/evolution/sequences/CanonicalSequence.java b/src/jebl/evolution/sequences/CanonicalSequence.java
new file mode 100644
index 0000000..bfd5bb0
--- /dev/null
+++ b/src/jebl/evolution/sequences/CanonicalSequence.java
@@ -0,0 +1,194 @@
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.util.AttributableHelper;
+
+import java.util.Set;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A default implementation of the Sequence interface
+ * that converts sequence characters to
+ * States such that calling getString() will always return
+ * uppercase residues with nucleotide U residues converted to T
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: BasicSequence.java 641 2007-02-16 11:56:21Z rambaut $
+ */
+public class CanonicalSequence implements Sequence {
+
+ /**
+ * Creates a sequence with a name corresponding to the taxon name.
+ *
+ * Use CharSequence so both a String and a StringBuilder are fine
+ *
+ * @param taxon
+ * @param sequenceString
+ */
+
+ public CanonicalSequence(SequenceType sequenceType, Taxon taxon, CharSequence sequenceString) {
+
+ if (sequenceType == null) {
+ throw new IllegalArgumentException("sequenceType is not allowed to be null");
+ }
+ if (taxon == null) {
+ throw new IllegalArgumentException("taxon is not allowed to be null");
+ }
+
+ this.sequenceType = sequenceType;
+ this.taxon = taxon;
+ final int len = sequenceString.length();
+ this.sequence = new byte[len];
+
+ for (int i = 0; i < len; i++) {
+ State state = sequenceType.getState(sequenceString.charAt(i));
+
+ if (state == null) {
+ // Something is wrong. Keep original length by inserting an unknown state
+ state = sequenceType.getUnknownState();
+ }
+ sequence[i] = (byte)state.getIndex();
+ }
+ }
+
+ /**
+ * Creates a sequence with a name corresponding to the taxon name
+ *
+ * @param taxon
+ * @param sequenceType
+ * @param states
+ */
+ public CanonicalSequence(SequenceType sequenceType, Taxon taxon, State[] states) {
+
+ this.sequenceType = sequenceType;
+ this.taxon = taxon;
+ this.sequence = new byte[states.length];
+ for (int i = 0; i < sequence.length; i++) {
+ sequence[i] = (byte)states[i].getIndex();
+ }
+ }
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ public SequenceType getSequenceType() {
+ return sequenceType;
+ }
+
+ /**
+ * @return a string representing the sequence of symbols.
+ */
+ public String getString() {
+ StringBuilder buffer = new StringBuilder(sequence.length);
+ for (int i : sequence) {
+ buffer.append(sequenceType.getState(i).getCode());
+ }
+ return buffer.toString();
+ }
+
+ public String getCleanString() {
+ StringBuilder buffer = new StringBuilder(sequence.length);
+ for (int i : sequence) {
+ State state = sequenceType.getState(i);
+ if (state.isAmbiguous() || state.isGap()) continue;
+ buffer.append(sequenceType.getState(i).getCode());
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * @return an array of state objects.
+ */
+ public State[] getStates() {
+ return sequenceType.toStateArray(sequence);
+ }
+
+ public byte[] getStateIndices() {
+ return sequence;
+ }
+
+ /**
+ * @return the state at site.
+ */
+ public State getState(int site) {
+ return sequenceType.getState(sequence[site]);
+ }
+
+ /**
+ * Returns the length of the sequence
+ *
+ * @return the length
+ */
+ public int getLength() {
+ return sequence.length;
+ }
+
+ /**
+ * @return that taxon that this sequence represents (primarily used to match sequences with tree nodes)
+ */
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ /**
+ * Sequences are compared by their taxa
+ *
+ * @param o another sequence
+ * @return an integer
+ */
+ public int compareTo(Object o) {
+ return taxon.compareTo(((Sequence) o).getTaxon());
+ }
+
+ public String toString() {
+ return getString();
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if (helper != null) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ private AttributableHelper helper = null;
+
+ // private members
+
+ private final Taxon taxon;
+ private final SequenceType sequenceType;
+ private final byte[] sequence;
+
+ // private Map<String, Object> attributeMap = null;
+}
diff --git a/src/jebl/evolution/sequences/CodonState.java b/src/jebl/evolution/sequences/CodonState.java
new file mode 100644
index 0000000..ababa1c
--- /dev/null
+++ b/src/jebl/evolution/sequences/CodonState.java
@@ -0,0 +1,30 @@
+/*
+ * CodonState.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: CodonState.java 225 2006-02-16 14:50:36Z rambaut $
+ */
+public final class CodonState extends State {
+
+ CodonState(String name, String stateCode, int index) {
+ super(name, stateCode, index);
+ }
+
+ CodonState(String name, String stateCode, int index, CodonState[] ambiguities) {
+ super(name, stateCode, index, ambiguities);
+ }
+
+ public boolean isGap() {
+ return this == Codons.GAP_STATE;
+ }
+}
diff --git a/src/jebl/evolution/sequences/Codons.java b/src/jebl/evolution/sequences/Codons.java
new file mode 100644
index 0000000..2ff3c94
--- /dev/null
+++ b/src/jebl/evolution/sequences/Codons.java
@@ -0,0 +1,134 @@
+/*
+ * Codons.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Codons.java 668 2007-03-26 06:47:40Z matt_kearse $
+ */
+public final class Codons {
+ public static final String NAME = "codon";
+
+ public static final int CANONICAL_STATE_COUNT = 64;
+ public static final int STATE_COUNT = 66;
+
+ public static final CodonState[] CANONICAL_STATES;
+ public static final CodonState[] STATES;
+
+ // This bit of static code creates the 64 canonical codon states
+ static {
+ CANONICAL_STATES = new CodonState[CANONICAL_STATE_COUNT];
+ char[] nucs = new char[] { 'A', 'C', 'G', 'T' };
+ int x = 0;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ for (int k = 0; k < 4; k++) {
+ String code = "" + nucs[i] + nucs[j] + nucs[k];
+ CANONICAL_STATES[x] = new CodonState(code, code, x);
+ x++;
+ }
+ }
+ }
+
+ }
+
+ public static final CodonState UNKNOWN_STATE = new CodonState("?", "???", 64, CANONICAL_STATES);
+ public static final CodonState GAP_STATE = new CodonState("-", "---", 65, CANONICAL_STATES);
+
+ public static int getStateCount() { return STATE_COUNT; }
+
+ public static List<State> getStates() { return Collections.unmodifiableList(Arrays.asList((State[])STATES)); }
+
+ public static int getCanonicalStateCount() { return CANONICAL_STATE_COUNT; }
+
+ public static List<State> getCanonicalStates() { return Collections.unmodifiableList(Arrays.asList((State[])CANONICAL_STATES)); }
+
+ public static CodonState getState(NucleotideState nucleotide1, NucleotideState nucleotide2, NucleotideState nucleotide3) {
+ if (nucleotide1.isGap() && nucleotide2.isGap() && nucleotide3.isGap()) {
+ return GAP_STATE;
+ }
+
+ if (nucleotide1.isAmbiguous() || nucleotide2.isAmbiguous() || nucleotide3.isAmbiguous()) {
+ return UNKNOWN_STATE;
+ }
+
+ String code = nucleotide1.getCode() + nucleotide2.getCode() + nucleotide3.getCode();
+ return statesByCode.get(code);
+ }
+
+ /**
+ * Gets the state object for the given code. Returns null if the code is illegal.
+ * @param code a three-character string of nucleotides in uppercase
+ * @return the state
+ */
+ public static CodonState getState(String code) {
+ code=code.toUpperCase().replace('U','T');
+ return statesByCode.get(code);
+ }
+
+ public static CodonState getState(int index) {
+ return STATES[index];
+ }
+
+ public static CodonState getUnknownState() { return UNKNOWN_STATE; }
+
+ public static CodonState getGapState() { return GAP_STATE; }
+
+ public static boolean isUnknown(CodonState state) { return state == UNKNOWN_STATE; }
+
+ public static boolean isGap(CodonState state) { return state == GAP_STATE; }
+
+ public static NucleotideState[] toNucleotides(CodonState state) {
+ NucleotideState[] nucs = new NucleotideState[3];
+ String code = state.getCode();
+ nucs[0] = Nucleotides.getState(code.charAt(0));
+ nucs[1] = Nucleotides.getState(code.charAt(1));
+ nucs[2] = Nucleotides.getState(code.charAt(2));
+ return nucs;
+ }
+
+ public static CodonState[] toStateArray(String sequenceString) {
+ int n = sequenceString.length() / 3;
+ CodonState[] seq = new CodonState[n];
+ for (int i = 0; i < n; i++) {
+ seq[i] = getState(sequenceString.substring(i * 3, (i * 3) + 3));
+ }
+ return seq;
+ }
+
+ public static CodonState[] toStateArray(byte[] indexArray) {
+ CodonState[] seq = new CodonState[indexArray.length];
+ for (int i = 0; i < seq.length; i++) {
+ seq[i] = getState(indexArray[i]);
+ }
+ return seq;
+ }
+
+ private static final Map<String, CodonState> statesByCode;
+
+ // now create the complete codon state array
+ static {
+ STATES = new CodonState[STATE_COUNT];
+ for (int i = 0; i < 64; i++) {
+ STATES[i] = CANONICAL_STATES[i];
+ }
+ STATES[64] = UNKNOWN_STATE;
+ STATES[65] = GAP_STATE;
+
+ statesByCode = new HashMap<String, CodonState>();
+ for (int i = 0; i < STATES.length; i++) {
+ statesByCode.put(STATES[i].getCode(), STATES[i]);
+ }
+ }
+
+}
diff --git a/src/jebl/evolution/sequences/FilteredSequence.java b/src/jebl/evolution/sequences/FilteredSequence.java
new file mode 100644
index 0000000..0e0b250
--- /dev/null
+++ b/src/jebl/evolution/sequences/FilteredSequence.java
@@ -0,0 +1,134 @@
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author rambaut
+ * @author Alexei Drummond
+ * @version $Id: FilteredSequence.java 641 2007-02-16 11:56:21Z rambaut $
+ */
+public abstract class FilteredSequence implements Sequence {
+ /**
+ * Creates a FilteredSequence wrapper to the given source sequence.
+ *
+ * @param source
+ */
+ public FilteredSequence(Sequence source) {
+
+ this.source = source;
+ }
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ public SequenceType getSequenceType() {
+ return source.getSequenceType();
+ }
+
+ /**
+ * @return a string representing the sequence of symbols.
+ */
+ public String getString() {
+ if (sequence == null) {
+ sequence = filterSequence(source);
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ for (State aSequence : sequence) {
+ buffer.append(aSequence.getCode());
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * @return an array of state objects.
+ */
+ public State[] getStates() {
+ if (sequence == null) {
+ sequence = filterSequence(source);
+ }
+ return sequence;
+ }
+
+ public byte[] getStateIndices() {
+ if (sequence == null) {
+ sequence = filterSequence(source);
+ }
+ byte[] stateIndices = new byte[sequence.length];
+ int i = 0;
+ for (State state : sequence) {
+ stateIndices[i] = (byte)state.getIndex();
+ i++;
+ }
+ return stateIndices;
+ }
+
+ /**
+ * @return the state at site.
+ */
+ public State getState(int site) {
+ if (sequence == null) {
+ sequence = filterSequence(source);
+ }
+ return sequence[site];
+ }
+
+ /**
+ * Returns the length of the sequence
+ *
+ * @return the length
+ */
+ public int getLength() {
+ if (sequence == null) {
+ sequence = filterSequence(source);
+ }
+ return sequence.length;
+ }
+
+ protected abstract State[] filterSequence(Sequence source);
+
+ /**
+ * @return that taxon that this sequence represents
+ */
+ public Taxon getTaxon() {
+ return source.getTaxon();
+ }
+
+ public int compareTo(Object o) {
+ return source.compareTo(o);
+ }
+
+ public String toString() {
+ return getString();
+ }
+
+ // Attributable implementation
+
+ public void setAttribute(String name, Object value) {
+ source.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ return source.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ source.removeAttribute(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ return source.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ return source.getAttributeMap();
+ }
+
+ // private members
+
+ private final Sequence source;
+ private State[] sequence = null;
+}
diff --git a/src/jebl/evolution/sequences/GaplessSequence.java b/src/jebl/evolution/sequences/GaplessSequence.java
new file mode 100644
index 0000000..69ff2b0
--- /dev/null
+++ b/src/jebl/evolution/sequences/GaplessSequence.java
@@ -0,0 +1,18 @@
+package jebl.evolution.sequences;
+
+/**
+ * @author rambaut
+ * Date: Jul 27, 2005
+ * Time: 12:48:31 AM
+ */
+public class GaplessSequence extends FilteredSequence {
+
+ public GaplessSequence(Sequence source) {
+ super(source);
+ }
+
+ protected State[] filterSequence(Sequence source) {
+ return jebl.evolution.sequences.Utils.stripGaps(source.getStates());
+ }
+
+}
diff --git a/src/jebl/evolution/sequences/GeneticCode.java b/src/jebl/evolution/sequences/GeneticCode.java
new file mode 100644
index 0000000..cd1cb20
--- /dev/null
+++ b/src/jebl/evolution/sequences/GeneticCode.java
@@ -0,0 +1,298 @@
+/*
+ * GeneticCode.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.sequences;
+
+import java.util.*;
+
+/**
+ * A set of standard genetic codes.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: GeneticCode.java 719 2007-05-31 04:46:40Z matt_kearse $
+ */
+
+public final class GeneticCode {
+ /**
+ * Standard genetic code tables from GENBANK
+ * Nucleotides go A, C, G, T - Note: this is not the order used by the Genbank web site
+ * With the first codon position most significant (i.e. AAA, AAC, AAG, AAT, ACA, etc.).
+ *
+ * The codes for the individual amino acids can be found in AminoAcids.java;
+ * For example, "*" stands for a stop codon (AminoAcids.STOP_STATE)
+ */
+ private static final String[] GENETIC_CODE_TABLES = {
+ // Universal
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSS*CWCLFLF",
+ // Vertebrate Mitochondrial
+ "KNKNTTTT*S*SMIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Yeast
+ "KNKNTTTTRSRSMIMIQHQHPPPPRRRRTTTTEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Mold Protozoan Mitochondrial
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Mycoplasma
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Invertebrate Mitochondrial
+ "KNKNTTTTSSSSMIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Ciliate
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVVQYQYSSSS*CWCLFLF",
+ // Echinoderm Mitochondrial
+ "NNKNTTTTSSSSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Euplotid Nuclear
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSCCWCLFLF",
+ // Bacterial
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSS*CWCLFLF",
+ // Alternative Yeast
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLSLEDEDAAAAGGGGVVVV*Y*YSSSS*CWCLFLF",
+ // Ascidian Mitochondrial
+ "KNKNTTTTGSGSMIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*Y*YSSSSWCWCLFLF",
+ // Flatworm Mitochondrial
+ "NNKNTTTTSSSSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVVYY*YSSSSWCWCLFLF",
+ // Blepharisma Nuclear
+ "KNKNTTTTRSRSIIMIQHQHPPPPRRRRLLLLEDEDAAAAGGGGVVVV*YQYSSSS*CWCLFLF"
+ };
+
+ /**
+ * Names of the standard genetic code tables from GENBANK
+ */
+ private static final String[] GENETIC_CODE_NAMES = {
+ "universal", "vertebrateMitochondrial", "yeast", "moldProtozoanMitochondrial",
+ "mycoplasma", "invertebrateMitochondrial", "ciliate", "echinodermMitochondrial",
+ "euplotidNuclear", "bacterial", "alternativeYeast", "ascidianMitochondrial",
+ "flatwormMitochondrial", "blepharismaNuclear"
+ };
+
+ /**
+ * Descriptions of the standard genetic code tables from GENBANK
+ */
+ private static final String[] GENETIC_CODE_DESCRIPTIONS = {
+ "Universal", "Vertebrate Mitochondrial", "Yeast", "Mold Protozoan Mitochondrial",
+ "Mycoplasma", "Invertebrate Mitochondrial", "Ciliate", "Echinoderm Mitochondrial",
+ "Euplotid Nuclear", "Bacterial", "Alternative Yeast", "Ascidian Mitochondrial",
+ "Flatworm Mitochondrial", "Blepharisma Nuclear"
+ };
+
+
+ public static final GeneticCode UNIVERSAL = new GeneticCode(GeneticCode.UNIVERSAL_ID);
+ public static final GeneticCode VERTEBRATE_MT = new GeneticCode(GeneticCode.VERTEBRATE_MT_ID);
+ public static final GeneticCode YEAST = new GeneticCode(GeneticCode.YEAST_ID);
+ public static final GeneticCode MOLD_PROTOZOAN_MT = new GeneticCode(GeneticCode.MOLD_PROTOZOAN_MT_ID);
+ public static final GeneticCode MYCOPLASMA = new GeneticCode(GeneticCode.MYCOPLASMA_ID);
+ public static final GeneticCode INVERTEBRATE_MT = new GeneticCode(GeneticCode.INVERTEBRATE_MT_ID);
+ public static final GeneticCode CILIATE = new GeneticCode(GeneticCode.CILIATE_ID);
+ public static final GeneticCode ECHINODERM_MT = new GeneticCode(GeneticCode.ECHINODERM_MT_ID);
+ public static final GeneticCode EUPLOTID_NUC = new GeneticCode(GeneticCode.EUPLOTID_NUC_ID);
+ public static final GeneticCode BACTERIAL = new GeneticCode(GeneticCode.BACTERIAL_ID);
+ public static final GeneticCode ALT_YEAST = new GeneticCode(GeneticCode.ALT_YEAST_ID);
+ public static final GeneticCode ASCIDIAN_MT = new GeneticCode(GeneticCode.ASCIDIAN_MT_ID);
+ public static final GeneticCode FLATWORM_MT = new GeneticCode(GeneticCode.FLATWORM_MT_ID);
+ public static final GeneticCode BLEPHARISMA_NUC = new GeneticCode(GeneticCode.BLEPHARISMA_NUC_ID);
+
+ public static final GeneticCode[] GENETIC_CODES = {
+ UNIVERSAL, VERTEBRATE_MT, YEAST, MOLD_PROTOZOAN_MT, MYCOPLASMA, INVERTEBRATE_MT,
+ CILIATE, ECHINODERM_MT, EUPLOTID_NUC, BACTERIAL, ALT_YEAST, ASCIDIAN_MT,
+ FLATWORM_MT, BLEPHARISMA_NUC
+ };
+
+ private GeneticCode(int geneticCodeId) {
+
+ this.geneticCodeId = geneticCodeId;
+ String codeTable = GENETIC_CODE_TABLES[geneticCodeId];
+ Map<CodonState, AminoAcidState> translationMap = new TreeMap<CodonState, AminoAcidState>();
+
+ if (codeTable.length() != 64) {
+ throw new IllegalArgumentException("Code Table length does not match number of codon states");
+ }
+
+ for (int i = 0; i < codeTable.length(); i++) {
+ CodonState codonState = Codons.CANONICAL_STATES[i];
+ AminoAcidState aminoAcidState = AminoAcids.getState(codeTable.substring(i, i+1));
+ translationMap.put(codonState, aminoAcidState);
+ }
+ translationMap.put(Codons.getGapState(), AminoAcids.getGapState());
+ translationMap.put(Codons.getUnknownState(), AminoAcids.getUnknownState());
+
+ this.translationMap = Collections.unmodifiableMap(translationMap);
+ }
+
+ /**
+ * Returns the name of the genetic code
+ */
+ public String getName() {
+ return GENETIC_CODE_NAMES[geneticCodeId];
+ }
+
+ /**
+ * Returns the description of the genetic code
+ */
+ public String getDescription() {
+ return GENETIC_CODE_DESCRIPTIONS[geneticCodeId];
+ }
+
+ /**
+ * Returns the description of the genetic code
+ */
+ public String getCodeTable() {
+ return GENETIC_CODE_TABLES[geneticCodeId];
+ }
+
+ /**
+ * Returns the state associated with AminoAcid represented by codonState.
+ * Note that the state is the canonical state (generated combinatorially)
+ * @see AminoAcids
+ * @see Codons
+ * @return '?' if codon unknown
+ */
+ public AminoAcidState getTranslation(CodonState codonState) {
+ //System.out.println(codonState.getCode());
+ return translationMap.get(codonState);
+ }
+
+ /**
+ * Returns the state associated with AminoAcid represented by the three nucleotides.
+ * If one or more of the nucleotides are ambiguous, and all combinations translate to the
+ * same protein, then this method will return that protein
+ * @see AminoAcids
+ * @see Codons
+ * @return '?' if codon unknown
+ */
+ public AminoAcidState getTranslation(NucleotideState nucleotide1, NucleotideState nucleotide2, NucleotideState nucleotide3){
+ CodonState translateState = null;
+ if (nucleotide1.isGap() && nucleotide2.isGap() && nucleotide3.isGap()) {
+ translateState = Codons.GAP_STATE;
+ }
+
+ if (nucleotide1.isAmbiguous() || nucleotide2.isAmbiguous() || nucleotide3.isAmbiguous()) {
+ for(State a : nucleotide1.getCanonicalStates()){
+ for(State b : nucleotide2.getCanonicalStates()){
+ for(State c : nucleotide3.getCanonicalStates()){
+ //initial setup
+ if(translateState == null)
+ translateState = Codons.getState(a.getCode() + b.getCode() + c.getCode());
+ if(!translationMap.get(translateState).equals(translationMap.get(Codons.getState(a.getCode() + b.getCode() + c.getCode()))))
+ return translationMap.get(Codons.UNKNOWN_STATE);
+ }
+ }
+ }
+ return translationMap.get(translateState);
+ }
+
+ String code = nucleotide1.getCode() + nucleotide2.getCode() + nucleotide3.getCode();
+ translateState = Codons.getState(code);
+
+ return translationMap.get(translateState);
+ }
+
+ /**
+ * Returns the state associated with AminoAcid represented by the three nucleotides.
+ * If one or more of the nucleotides are ambiguous, and all combinations translate to the
+ * same protein, then this method will return that protein
+ * @param nucleotides a string consisting of exactly 3 residues in any case.
+ * @see AminoAcids
+ * @see Codons
+ * @return '?' if codon unknown
+ */
+ public AminoAcidState getTranslation(String nucleotides) {
+ if (nucleotides.length()!=3) throw new IllegalArgumentException("getTranslation requires a nucleotide triplet. (given "+nucleotides.length()+" characters)");
+ NucleotideState n1=Nucleotides.getState(nucleotides.charAt(0));
+ NucleotideState n2=Nucleotides.getState(nucleotides.charAt(1));
+ NucleotideState n3=Nucleotides.getState(nucleotides.charAt(2));
+ return getTranslation(n1,n2,n3);
+ }
+
+ /**
+ * Returns true if this is a start codon in this genetic code.
+ * WARNING: I don't know how to implement this. It only returns true
+ * for ATG currently. But according to Wikipedia, about 23% of E.Coli
+ * start codons are not ATG!
+ * @param codonState
+ * @return true if this is a start codon.
+ */
+ public boolean isStartCodon(CodonState codonState) {
+ if (codonState==null) return false; // to handle codons generated from ambiguous residues where it returns null from Codons.getState()
+ return codonState.getCode().equals("ATG");
+ }
+
+ /**
+ * Note that the state is the canonical state (generated combinatorially)
+ * @return whether the codonState is a stop codon
+ */
+ public boolean isStopCodon(CodonState codonState) {
+ if (codonState==null) return false; // to handle codons generated from ambiguous residues where it returns null from Codons.getState()
+ return (translationMap.get(codonState) == AminoAcids.STOP_STATE);
+ }
+
+ /**
+ * @return all the possible codons for a given amino acid
+ */
+ public Set<CodonState> getCodonsForAminoAcid(AminoAcidState aminoAcidState) {
+ Set<CodonState> aaSet = new HashSet<CodonState>();
+ for (CodonState state : translationMap.keySet()) {
+ if (translationMap.get(state) == aminoAcidState) {
+ aaSet.add(state);
+ }
+ }
+ return aaSet;
+ }
+
+ /**
+ * @return the codon states of stops.
+ */
+ public Set<CodonState> getStopCodons() {
+ Set<CodonState> stopSet = new HashSet<CodonState>();
+ for (CodonState state : translationMap.keySet()) {
+ if (isStopCodon(state)) {
+ stopSet.add(state);
+ }
+ }
+ return stopSet;
+ }
+
+ /**
+ * Returns the number of terminator amino acids.
+ */
+ public int getStopCodonCount() {
+ int count = 0;
+ for (AminoAcidState state : translationMap.values()) {
+ if (state == AminoAcids.STOP_STATE) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private final int geneticCodeId;
+ private final Map<CodonState, AminoAcidState> translationMap;
+
+ /**
+ * Constants used to refer to the built in code tables
+ */
+ private static final int UNIVERSAL_ID = 0;
+ private static final int VERTEBRATE_MT_ID = 1;
+ private static final int YEAST_ID = 2;
+ private static final int MOLD_PROTOZOAN_MT_ID = 3;
+ private static final int MYCOPLASMA_ID = 4;
+ private static final int INVERTEBRATE_MT_ID = 5;
+ private static final int CILIATE_ID = 6;
+ private static final int ECHINODERM_MT_ID = 7;
+ private static final int EUPLOTID_NUC_ID = 8;
+ private static final int BACTERIAL_ID = 9;
+ private static final int ALT_YEAST_ID = 10;
+ private static final int ASCIDIAN_MT_ID = 11;
+ private static final int FLATWORM_MT_ID = 12;
+ private static final int BLEPHARISMA_NUC_ID = 13;
+
+ public String toString() {
+ return getDescription();
+ }
+
+
+}
diff --git a/src/jebl/evolution/sequences/NucleotideState.java b/src/jebl/evolution/sequences/NucleotideState.java
new file mode 100644
index 0000000..a80e21e
--- /dev/null
+++ b/src/jebl/evolution/sequences/NucleotideState.java
@@ -0,0 +1,49 @@
+/*
+ * NucleotideState.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: NucleotideState.java 267 2006-03-21 02:36:55Z twobeers $
+ */
+public final class NucleotideState extends State {
+
+ NucleotideState(String name, String stateCode, int index) {
+ super(name, stateCode, index);
+ }
+
+ NucleotideState(String name, String stateCode, int index, NucleotideState[] ambiguities) {
+ super(name, stateCode, index, ambiguities);
+ }
+
+ public int compareTo(Object o) {
+ // throws ClassCastException on across-class comparison
+ NucleotideState that = (NucleotideState) o;
+ return super.compareTo(that);
+ }
+
+ // we do not need to override equals because there is only one
+ // unique instance of each nucleotide state - i.e. we can use ==
+ /*
+ public boolean equals(Object o) {
+ if (!(o instanceof NucleotideState))
+ return false;
+ return super.equals(o);
+ } */
+
+ public int hashCode() {
+ return 23 * super.hashCode() + 17;
+ }
+
+ public boolean isGap() {
+ return this == Nucleotides.GAP_STATE;
+ }
+}
diff --git a/src/jebl/evolution/sequences/Nucleotides.java b/src/jebl/evolution/sequences/Nucleotides.java
new file mode 100644
index 0000000..dd03881
--- /dev/null
+++ b/src/jebl/evolution/sequences/Nucleotides.java
@@ -0,0 +1,184 @@
+/*
+ * Nucleotides.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Nucleotides.java 673 2007-03-28 04:35:52Z matt_kearse $
+ */
+public final class Nucleotides {
+ public static final String NAME = "nucleotide";
+
+ public static final int CANONICAL_STATE_COUNT = 4;
+ public static final int STATE_COUNT = 17;
+
+ public static final NucleotideState A_STATE = new NucleotideState("A", "A", 0);
+ public static final NucleotideState C_STATE = new NucleotideState("C", "C", 1);
+ public static final NucleotideState G_STATE = new NucleotideState("G", "G", 2);
+ public static final NucleotideState T_STATE = new NucleotideState("T", "T", 3);
+
+ // The following line has been removed since it's never used and if it
+ // was used would cause all sorts of bugs with various analysis code.
+ // T_STATE represents either a U or a T depending on the context.
+ //public static final NucleotideState U_STATE = new NucleotideState("U", "U", 3);
+
+ public static final NucleotideState R_STATE = new NucleotideState("R", "R", 4, new NucleotideState[] {A_STATE, G_STATE});
+ public static final NucleotideState Y_STATE = new NucleotideState("Y", "Y", 5, new NucleotideState[] {C_STATE, T_STATE});
+ public static final NucleotideState M_STATE = new NucleotideState("M", "M", 6, new NucleotideState[] {A_STATE, C_STATE});
+ public static final NucleotideState W_STATE = new NucleotideState("W", "W", 7, new NucleotideState[] {A_STATE, T_STATE});
+ public static final NucleotideState S_STATE = new NucleotideState("S", "S", 8, new NucleotideState[] {C_STATE, G_STATE});
+ public static final NucleotideState K_STATE = new NucleotideState("K", "K", 9, new NucleotideState[] {G_STATE, T_STATE});
+ public static final NucleotideState B_STATE = new NucleotideState("B", "B", 10, new NucleotideState[] {C_STATE, G_STATE, T_STATE});
+ public static final NucleotideState D_STATE = new NucleotideState("D", "D", 11, new NucleotideState[] {A_STATE, G_STATE, T_STATE});
+ public static final NucleotideState H_STATE = new NucleotideState("H", "H", 12, new NucleotideState[] {A_STATE, C_STATE, T_STATE});
+ public static final NucleotideState V_STATE = new NucleotideState("V", "V", 13, new NucleotideState[] {A_STATE, C_STATE, G_STATE});
+ public static final NucleotideState N_STATE = new NucleotideState("N", "N", 14, new NucleotideState[] {A_STATE, C_STATE, G_STATE, T_STATE});
+
+ public static final NucleotideState UNKNOWN_STATE = new NucleotideState("?", "?", 15, new NucleotideState[] {A_STATE, C_STATE, G_STATE, T_STATE});
+ public static final NucleotideState GAP_STATE = new NucleotideState("-", "-", 16, new NucleotideState[] {A_STATE, C_STATE, G_STATE, T_STATE});
+
+ public static final NucleotideState[] CANONICAL_STATES = new NucleotideState[] {
+ A_STATE, C_STATE, G_STATE, T_STATE
+ };
+
+ public static final NucleotideState[] STATES = new NucleotideState[] {
+ A_STATE, C_STATE, G_STATE, T_STATE,
+ R_STATE, Y_STATE, M_STATE, W_STATE,
+ S_STATE, K_STATE, B_STATE, D_STATE,
+ H_STATE, V_STATE, N_STATE, UNKNOWN_STATE, GAP_STATE
+ };
+
+ public static final NucleotideState[] COMPLEMENTARY_STATES = new NucleotideState[]{
+ T_STATE, G_STATE, C_STATE, A_STATE,
+ Y_STATE, R_STATE, K_STATE, W_STATE,
+ S_STATE, M_STATE, V_STATE, H_STATE,
+ D_STATE, B_STATE, N_STATE, UNKNOWN_STATE, GAP_STATE
+ };
+ private static final int STATES_BY_CODE_SIZE = 128;
+
+ // Static utility functions
+
+ public static int getStateCount() { return STATE_COUNT; }
+
+ /**
+ *
+ * @return A list of all possible states, including the gap and ambiguity states.
+ */
+ public static List<State> getStates() { return Collections.unmodifiableList(Arrays.asList((State[])STATES)); }
+
+ public static int getCanonicalStateCount() { return CANONICAL_STATE_COUNT; }
+
+ public static List<NucleotideState> getCanonicalStates() { return Collections.unmodifiableList(Arrays.asList(CANONICAL_STATES)); }
+
+ public static NucleotideState getState(char code) {
+ if (code < 0 || code >= STATES_BY_CODE_SIZE) {
+ return null;
+ }
+ return statesByCode[code];
+ }
+
+ public static NucleotideState getState(String code) {
+ return getState(code.charAt(0));
+ }
+
+ public static NucleotideState getState(int index) {
+ return STATES[index];
+ }
+
+ public static NucleotideState getUnknownState() { return UNKNOWN_STATE; }
+
+ public static NucleotideState getGapState() { return GAP_STATE; }
+
+ public static boolean isUnknown(NucleotideState state) { return state == UNKNOWN_STATE; }
+
+ public static boolean isGap(NucleotideState state) { return state == GAP_STATE; }
+
+ // states must not be ambiguous/gaps nor equal
+ // transition A<==>G or C<==>T
+ public static boolean isTransition(State state1, State state2) {
+ // use A,G is even and C,T are odd
+ return ((state1.getIndex() + state2.getIndex()) & 0x1) == 0; // checks if sum is even
+ }
+
+ // states must not be ambiguous/gaps nor equal
+ public static boolean isTransversion(State state1, State state2) {
+ return !isTransition(state1, state2);
+ }
+
+ public static boolean isPurine(State state) {
+ if (state.isAmbiguous()) {
+ // return true only if all its ambiguities are isPurine()
+ for (State state1 : state.getCanonicalStates()) {
+ if(! isPurine(state1)) return false;
+ }
+ return true;
+ }
+ return state == A_STATE || state == G_STATE;
+ }
+
+ public static boolean isPyrimidine(State state) {
+ if (state.isAmbiguous()) {
+ // return true only if all its ambiguities are isPyrimidine()
+ for (State state1 : state.getCanonicalStates()) {
+ if (! isPyrimidine(state1)) return false;
+ }
+ return true;
+ }
+ return state == C_STATE || state == T_STATE;
+ }
+ public static boolean isGCstate(State state) {
+ return ( state == G_STATE || state == C_STATE || state == S_STATE);
+ }
+
+ public static boolean isATstate(State state) {
+ return (state == A_STATE || state == T_STATE || state == W_STATE);
+ }
+
+ public String getName() { return "Nucleotides"; }
+
+ public static NucleotideState[] toStateArray(String sequenceString) {
+ NucleotideState[] seq = new NucleotideState[sequenceString.length()];
+ for (int i = 0; i < seq.length; i++) {
+ seq[i] = getState(sequenceString.charAt(i));
+ }
+ return seq;
+ }
+
+ public static NucleotideState[] toStateArray(byte[] indexArray) {
+ NucleotideState[] seq = new NucleotideState[indexArray.length];
+ for (int i = 0; i < seq.length; i++) {
+ seq[i] = getState(indexArray[i]);
+ }
+ return seq;
+ }
+
+ private static final NucleotideState[] statesByCode;
+
+ static {
+ statesByCode = new NucleotideState[STATES_BY_CODE_SIZE];
+ for (int i = 0; i < statesByCode.length; i++) {
+ // Undefined characters are mapped to null
+ statesByCode[i] = null;
+ }
+
+ for (NucleotideState state : STATES) {
+ statesByCode[state.getCode().charAt(0)] = state;
+ statesByCode[Character.toLowerCase(state.getCode().charAt(0))] = state;
+ }
+
+ statesByCode['u'] = T_STATE;
+ statesByCode['U'] = T_STATE;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/sequences/Sequence.java b/src/jebl/evolution/sequences/Sequence.java
new file mode 100644
index 0000000..d7dd66c
--- /dev/null
+++ b/src/jebl/evolution/sequences/Sequence.java
@@ -0,0 +1,59 @@
+/*
+ * Sequence.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.util.Attributable;
+
+/**
+ * A biomolecular sequence.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Sequence.java 365 2006-06-28 07:34:56Z pepster $
+ */
+public interface Sequence extends Attributable, Comparable {
+
+ /**
+ * @return the taxon that this sequence represents (primarily used to match sequences with tree nodes)
+ */
+ Taxon getTaxon();
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ SequenceType getSequenceType();
+
+ /**
+ * @return a string representing the sequence of symbols.
+ */
+ String getString();
+
+ /**
+ * @return an array of state objects.
+ */
+ State[] getStates();
+
+ /**
+ * @return an array of state indices.
+ */
+ byte[] getStateIndices();
+
+ /**
+ * @return the state at site.
+ */
+ State getState(int site);
+
+ /**
+ * Get the length of the sequence
+ * @return the length
+ */
+ int getLength();
+}
diff --git a/src/jebl/evolution/sequences/SequenceStateException.java b/src/jebl/evolution/sequences/SequenceStateException.java
new file mode 100644
index 0000000..7a5f572
--- /dev/null
+++ b/src/jebl/evolution/sequences/SequenceStateException.java
@@ -0,0 +1,21 @@
+/*
+ * SequenceStateException.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: SequenceStateException.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class SequenceStateException extends Exception {
+ public SequenceStateException(String s) {
+ super(s);
+ }
+}
diff --git a/src/jebl/evolution/sequences/SequenceTester.java b/src/jebl/evolution/sequences/SequenceTester.java
new file mode 100644
index 0000000..c5f7662
--- /dev/null
+++ b/src/jebl/evolution/sequences/SequenceTester.java
@@ -0,0 +1,63 @@
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+/**
+ *
+ * @author MattK
+ * @version $Id: SequenceTester.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class SequenceTester {
+ public static String readSequence(String name) {
+ try {
+ BufferedReader br1 = new BufferedReader(new FileReader(name));
+ String sq1 = "";
+
+ String line = br1.readLine();
+ if (!((line.substring(0, 1)).equals(">")))
+ sq1 += line;
+ line = br1.readLine();
+ while (line != null) {
+ if (line.substring(0, 1).equals(">")) break;
+ sq1 = sq1.concat(line.trim());
+ line = br1.readLine();
+ }
+ return sq1;
+ }
+ catch (Exception e) {
+ return "";
+ }
+
+
+ }
+
+ public static Sequence getTestSequence1() {
+
+ return new BasicSequence(SequenceType.AMINO_ACID, Taxon.getTaxon("sequence1"),
+ "PLQMKKTAFTLLLFIALTLTTSPLVNGSEKSEEINEKDLRKKSELQGTALGNLKQIYYYNEKAKTENKES" +
+ "HDQFLQHTILFKGFFTDHSWYNDLLVDFDSKDIVDKYKGKKVDLYGAYYGYQCAGGTPNKTACMYGGVTL" +
+ "HDNNRLTEEKKVPINLWLDGKQNTVPLETVKTNKKNVTVQELDLQARRYLQEKYNLYNSDVFDGKVQRGL" +
+ "IVFHTSTEPSVNYDLFGAQGQYSNTLLRIYRDNKTISSENMHIDIYLYTSY");
+ }
+
+ public static Sequence getTestSequence2() {
+ return new BasicSequence(SequenceType.AMINO_ACID, Taxon.getTaxon("sequence2"),
+ "MYKRLFISHVILIFALILVISTPNVLAESQPDPKPDELHKSSKFTGLMENMKVLYDDNHVSAINVKSIDQ" +
+ "FLYFDLIYSIKDTKLGNYDNVRVEFKNKDLADKYKDKYVDVFGANYYYQCYFSKKTNDINSHQTDKRKTC" +
+ "MYGGVTEHNGNQLDKYRSITVRVFEDGKNLLSFDVQTNKKKVTAQELDYLTRHYLVKNKKLYEFNNSPYE" +
+ "TGYIKFIENENSFWYDMMPAPGDKFDQSKYLMMYNDNKMVDSKDVKIEVYLTTKKK");
+ }
+
+ public static String getTestSequence1(String[] arguments) {
+ if (arguments.length> 0) return readSequence (arguments [0]);
+ return getTestSequence1().getString();
+ }
+ public static String getTestSequence2(String[] arguments) {
+ if (arguments.length> 1) return readSequence (arguments [1]);
+ return getTestSequence2().getString();
+ }
+
+}
diff --git a/src/jebl/evolution/sequences/SequenceType.java b/src/jebl/evolution/sequences/SequenceType.java
new file mode 100644
index 0000000..d0c9fc1
--- /dev/null
+++ b/src/jebl/evolution/sequences/SequenceType.java
@@ -0,0 +1,355 @@
+/*
+ * SequenceType.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.evolution.sequences;
+
+import java.util.List;
+
+/**
+ * Interface for sequences data types.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: SequenceType.java 551 2006-12-04 03:05:01Z twobeers $
+ */
+public interface SequenceType {
+
+ /**
+ * Get number of states including ambiguous states
+ *
+ * @return number of states
+ */
+ int getStateCount();
+
+ /**
+ * Get a list of states ordered by their indices.
+ *
+ * @return a list of states
+ */
+ List<? extends State> getStates();
+
+ /**
+ * Get number of canonical states
+ *
+ * @return number of states
+ */
+ int getCanonicalStateCount();
+
+ /**
+ * Get a list of canonical states ordered by their indices.
+ *
+ * @return a list of states
+ */
+ List<? extends State> getCanonicalStates();
+
+ /**
+ * Get state corresponding to a string code
+ *
+ * @param code a string code
+ * @return the state with the given code, or null if there is no such state
+ */
+ State getState(String code);
+
+ /**
+ * Get state whose code is the one-character string consisting only of code.
+ *
+ * @param code
+ * @return the state with the given code, or null if there is no such state
+ */
+ State getState(char code);
+
+ /**
+ * @return the length, in characters, of a state, when encoded as a string.
+ * i.e. 1 for Nucleotides and AminoAcids and 3 for Codons.
+ */
+ int getCodeLength();
+
+ /**
+ * Get state corresponding to a state index
+ *
+ * @param index a state index
+ * @return the state
+ */
+ State getState(int index);
+
+ /**
+ * Get state corresponding to an unknown
+ *
+ * @return the state
+ */
+ State getUnknownState();
+
+ /**
+ * Get state corresponding to a gap
+ *
+ * @return state
+ */
+ State getGapState();
+
+ /**
+ * @return true if this state is an unknown state
+ */
+ boolean isUnknown(State state);
+
+ /**
+ * @return true if this state is a gap
+ */
+ boolean isGap(State state);
+
+ /**
+ * name of data type
+ *
+ * @return string describing the data type
+ */
+ String getName();
+
+ /**
+ * @return datatype inside a nexus characters block, if any.
+ */
+ String getNexusDataType();
+
+ /**
+ * Converts a string of state codes into an array of State objects for this SequenceType
+ *
+ * @param sequenceString
+ * @return the State array
+ */
+ State[] toStateArray(String sequenceString);
+
+ /**
+ * Converts an array of state indices into an array of State objects for this SequenceType
+ *
+ * @param indexArray
+ * @return the State array
+ */
+ State[] toStateArray(byte[] indexArray);
+
+ public static final SequenceType NUCLEOTIDE = new SequenceType() {
+ public int getStateCount() {
+ return Nucleotides.getStateCount();
+ }
+
+ public List<State> getStates() {
+ return Nucleotides.getStates();
+ }
+
+ public int getCanonicalStateCount() {
+ return Nucleotides.getCanonicalStateCount();
+ }
+
+ public List<NucleotideState> getCanonicalStates() {
+ return Nucleotides.getCanonicalStates();
+ }
+
+ public State getState(String code) {
+ return Nucleotides.getState(code);
+ }
+
+ public State getState(char code) {
+ return Nucleotides.getState(code);
+ }
+
+ public int getCodeLength() {
+ return 1;
+ }
+
+ public State getState(int index) {
+ return Nucleotides.getState(index);
+ }
+
+ public State getUnknownState() {
+ return Nucleotides.getUnknownState();
+ }
+
+ public State getGapState() {
+ return Nucleotides.getGapState();
+ }
+
+ public boolean isUnknown(State state) {
+ return Nucleotides.isUnknown((NucleotideState) state);
+ }
+
+ public boolean isGap(State state) {
+ return Nucleotides.isGap((NucleotideState) state);
+ }
+
+ public String getName() {
+ return Nucleotides.NAME;
+ }
+
+ public String getNexusDataType() {
+ return "dna";
+ }
+
+ public State[] toStateArray(String sequenceString) {
+ return Nucleotides.toStateArray(sequenceString);
+ }
+
+ public State[] toStateArray(byte[] indexArray) {
+ return Nucleotides.toStateArray(indexArray);
+ }
+
+ public String toString() {
+ return getName();
+ }
+ };
+
+ public static final SequenceType AMINO_ACID = new SequenceType() {
+ public int getStateCount() {
+ return AminoAcids.getStateCount();
+ }
+
+ public List<AminoAcidState> getStates() {
+ return AminoAcids.getStates();
+ }
+
+ public int getCanonicalStateCount() {
+ return AminoAcids.getCanonicalStateCount();
+ }
+
+ public List<State> getCanonicalStates() {
+ return AminoAcids.getCanonicalStates();
+ }
+
+ public State getState(String code) {
+ return AminoAcids.getState(code);
+ }
+
+ public State getState(char code) {
+ return AminoAcids.getState(code);
+ }
+
+ public int getCodeLength() {
+ return 1;
+ }
+
+ public State getState(int index) {
+ return AminoAcids.getState(index);
+ }
+
+ public State getUnknownState() {
+ return AminoAcids.getUnknownState();
+ }
+
+ public State getGapState() {
+ return AminoAcids.getGapState();
+ }
+
+ public boolean isUnknown(State state) {
+ return AminoAcids.isUnknown((AminoAcidState) state);
+ }
+
+ public boolean isGap(State state) {
+ return AminoAcids.isGap((AminoAcidState) state);
+ }
+
+ public String getName() {
+ return AminoAcids.NAME;
+ }
+
+ public String getNexusDataType() {
+ return "protein";
+ }
+
+ public State[] toStateArray(String sequenceString) {
+ return AminoAcids.toStateArray(sequenceString);
+ }
+
+ public State[] toStateArray(byte[] indexArray) {
+ return AminoAcids.toStateArray(indexArray);
+ }
+
+ public String toString() {
+ return getName();
+ }
+ };
+
+ public static final SequenceType CODON = new SequenceType() {
+ public int getStateCount() {
+ return Codons.getStateCount();
+ }
+
+ public List<State> getStates() {
+ return Codons.getStates();
+ }
+
+ public int getCanonicalStateCount() {
+ return Codons.getCanonicalStateCount();
+ }
+
+ public List<State> getCanonicalStates() {
+ return Codons.getCanonicalStates();
+ }
+
+ public State getState(String code) {
+ return Codons.getState(code);
+ }
+
+ public State getState(char code) {
+ // if anybody searches for a string of length one he should get a null from getState(String) anyway
+ return null;
+ }
+
+ public int getCodeLength() {
+ return 3;
+ }
+
+ public State getState(int index) {
+ return Codons.getState(index);
+ }
+
+ public State getUnknownState() {
+ return Codons.getUnknownState();
+ }
+
+ public State getGapState() {
+ return Codons.getGapState();
+ }
+
+ public boolean isUnknown(State state) {
+ return Codons.isUnknown((CodonState) state);
+ }
+
+ public boolean isGap(State state) {
+ return Codons.isGap((CodonState) state);
+ }
+
+ public String getName() {
+ return Codons.NAME;
+ }
+
+ public String getNexusDataType() {
+ return "dna";
+ } // Guessing here (JH)
+
+ public State[] toStateArray(String sequenceString) {
+ return Codons.toStateArray(sequenceString);
+ }
+
+ public State[] toStateArray(byte[] indexArray) {
+ return Codons.toStateArray(indexArray);
+ }
+
+ public String toString() {
+ return getName();
+ }
+ };
+
+ public class Utils {
+
+ public static String getAlphabet(SequenceType sequenceType) {
+
+ StringBuilder buffer = new StringBuilder();
+ for (State state : sequenceType.getCanonicalStates()) {
+ buffer.append(state.getCode());
+ }
+ return buffer.toString();
+ }
+ }
+}
diff --git a/src/jebl/evolution/sequences/Sequences.java b/src/jebl/evolution/sequences/Sequences.java
new file mode 100644
index 0000000..0282a64
--- /dev/null
+++ b/src/jebl/evolution/sequences/Sequences.java
@@ -0,0 +1,28 @@
+/*
+ * Sequences.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Set;
+
+/**
+ * A set of sequence objects.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Sequences.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public interface Sequences {
+
+ Set<Sequence> getSequences();
+
+ Sequence getSequence(Taxon taxon);
+}
diff --git a/src/jebl/evolution/sequences/State.java b/src/jebl/evolution/sequences/State.java
new file mode 100644
index 0000000..b165a1f
--- /dev/null
+++ b/src/jebl/evolution/sequences/State.java
@@ -0,0 +1,92 @@
+/*
+ * State.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: State.java 650 2007-03-12 20:09:10Z twobeers $
+ */
+public abstract class State implements Comparable {
+
+ State(String name, String stateCode, int index) {
+ this.name = name;
+ this.stateCode = stateCode;
+
+ // TT: is there any reason why instead of the next three lines we don't just say
+ // this.ambiguities = Collections.singleton(this) ?
+ List<State> ambiguities = new ArrayList<State>();
+ ambiguities.add(this);
+ this.ambiguities = Collections.unmodifiableSortedSet(new TreeSet<State>(ambiguities));
+ this.index = index;
+ }
+
+ State(String name, String stateCode, int index, State[] ambiguities) {
+ this.name = name;
+ this.stateCode = stateCode;
+ this.ambiguities = Collections.unmodifiableSortedSet(new TreeSet<State>(Arrays.asList(ambiguities)));
+ this.index = index;
+ }
+
+ public String getCode() {
+ return stateCode;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public String getName() { return name; }
+
+ public boolean isAmbiguous() {
+ return getCanonicalStates().size() > 1;
+ }
+
+ public Set<State> getCanonicalStates() {
+ return ambiguities;
+ }
+
+ /**
+ * @param other another state to check for the quality with.
+ * @return true if the other state is or possibly is equal to this state, taking ambiguities into account,
+ * i.e. if the ambiguity sets of this and the other state intersect.
+ */
+ public boolean possiblyEqual(State other) {
+ for (State state : getCanonicalStates()) {
+ for (State state1 : other.getCanonicalStates()) {
+ if(state.equals (state1)) return true;
+ }
+ }
+ return false;
+ }
+
+ public int compareTo(Object o) {
+ return index - ((State)o).index;
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof State) && (this.index == ((State) o).index);
+ }
+
+ public int hashCode() {
+ return index;
+ }
+
+ public String toString() { return stateCode; }
+
+ public abstract boolean isGap();
+
+ private String stateCode;
+ private String name;
+ private Set<State> ambiguities;
+ private int index;
+}
diff --git a/src/jebl/evolution/sequences/StateClassification.java b/src/jebl/evolution/sequences/StateClassification.java
new file mode 100644
index 0000000..2a60e5e
--- /dev/null
+++ b/src/jebl/evolution/sequences/StateClassification.java
@@ -0,0 +1,58 @@
+package jebl.evolution.sequences;
+
+import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: StateClassification.java 206 2006-02-02 17:49:56Z rambaut $
+ */
+public interface StateClassification {
+
+ String getName();
+
+ Set<String> getSetNames();
+ String getSetName(State state);
+ Set<State> getStateSet(String setName);
+
+ public class Default implements StateClassification {
+ public Default(String name, String[] setNames, State[][] stateSets) {
+ this.name = name;
+ int i = 0;
+ for (String setName : setNames) {
+ Set<State> stateSet = new HashSet<State>();
+ for (State state : stateSets[i]) {
+ stateSet.add(state);
+ }
+ stateMap.put(setName, stateSet);
+ i++;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<String> getSetNames() {
+ return stateMap.keySet();
+ }
+
+ public String getSetName(State state) {
+ for (String setName : getSetNames()) {
+ if (getStateSet(setName).contains(state)) return setName;
+ }
+ return null;
+ }
+
+ public Set<State> getStateSet(String setName) {
+ return stateMap.get(setName);
+ }
+
+ private final String name;
+
+ private final Map<String, Set<State>> stateMap = new HashMap<String, Set<State>>();
+ }
+}
diff --git a/src/jebl/evolution/sequences/TranslatedSequence.java b/src/jebl/evolution/sequences/TranslatedSequence.java
new file mode 100644
index 0000000..85fb758
--- /dev/null
+++ b/src/jebl/evolution/sequences/TranslatedSequence.java
@@ -0,0 +1,29 @@
+package jebl.evolution.sequences;
+
+/**
+ * @author rambaut
+ * Date: Jul 27, 2005
+ * Time: 12:48:31 AM
+ */
+public class TranslatedSequence extends FilteredSequence {
+
+ public TranslatedSequence(Sequence source, GeneticCode geneticCode) {
+ super(source);
+
+ this.geneticCode = geneticCode;
+ }
+
+ protected State[] filterSequence(Sequence source) {
+ return jebl.evolution.sequences.Utils.translate(source.getStates(), geneticCode);
+ }
+
+ /**
+ * @return the type of symbols that this sequence is made up of.
+ */
+ public SequenceType getSequenceType() {
+ return SequenceType.AMINO_ACID;
+ }
+
+ private final GeneticCode geneticCode;
+
+}
diff --git a/src/jebl/evolution/sequences/Utils.java b/src/jebl/evolution/sequences/Utils.java
new file mode 100644
index 0000000..70730fe
--- /dev/null
+++ b/src/jebl/evolution/sequences/Utils.java
@@ -0,0 +1,346 @@
+/*
+ * Utils.java
+ *
+ * (c) 2002-2005 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.sequences;
+
+import jebl.evolution.taxa.Taxon;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Utils.java 688 2007-04-12 15:16:25Z rambaut $
+ */
+public class Utils {
+
+ /**
+ * Translates each of a given sequence of {@link NucleotideState}s or {@link CodonState}s
+ * to the {@link AminoAcidState} corresponding to it under the given genetic code.
+ *
+ * Translation doesn't stop at stop codons; these are translated to {@link AminoAcids#STOP_STATE}.
+ * If translating from {@link jebl.evolution.sequences.NucleotideState} and the number
+ * of states is not a multiple of 3, then the excess states at the end are silently dropped.
+ *
+ * @param states States to translate; must all be of the same type, either NucleotideState
+ * or CodonState.
+ * @param geneticCode
+ * @return
+ */
+ public static AminoAcidState[] translate(final State[] states, GeneticCode geneticCode) {
+ if (states == null) throw new NullPointerException("States array is null");
+ if (states.length == 0) return new AminoAcidState[0];
+
+ if (states[0] instanceof NucleotideState) {
+ AminoAcidState[] translation = new AminoAcidState[states.length / 3];
+ for (int i = 0; i < translation.length; i++) {
+ CodonState state = Codons.getState((NucleotideState)states[i * 3],
+ (NucleotideState)states[(i * 3) + 1],
+ (NucleotideState)states[(i * 3) + 2]);
+ translation[i] = geneticCode.getTranslation(state);
+ }
+ return translation;
+ } else if (states[0] instanceof CodonState) {
+ AminoAcidState[] translation = new AminoAcidState[states.length];
+ for (int i = 0; i < translation.length; i++) {
+ translation[i] = geneticCode.getTranslation((CodonState)states[i]);
+ }
+ return translation;
+ } else {
+ throw new IllegalArgumentException("Given states are not nucleotides or codons so cannot be translated");
+ }
+ }
+
+ /**
+ * Is the given NucleotideSequence predominantly RNA?
+ * (i.e the more occurrences of "U" than "T")
+ * @param sequenceString the sequence string to inspect to determine if it's RNA
+ * @param maximumNonGapsToLookAt for performance reasons, only look at a maximum of this many non-gap residues in deciding if the sequence is predominantly RNA. Can be -1 or Integer.MAX_VALUE to look at the entire sequence.
+ * @return true if the given NucleotideSequence predominantly RNA
+ */
+ public static boolean isPredominantlyRNA(final CharSequence sequenceString, int maximumNonGapsToLookAt) {
+ int length = sequenceString.length();
+ int tCount = 0;
+ int uCount = 0;
+ if (maximumNonGapsToLookAt==-1) maximumNonGapsToLookAt=Integer.MAX_VALUE;
+ for (int i = 0; i < length && maximumNonGapsToLookAt > 0; i++) {
+ char c = sequenceString.charAt(i);
+ if (c != '-') maximumNonGapsToLookAt--;
+ if (c == 'T' || c == 't') tCount++;
+ if (c == 'U' || c == 'u') uCount++;
+ }
+ return uCount > tCount;
+ }
+
+ private static String reverseComplement(final String nucleotideSequence, boolean removeGaps) {
+ boolean predominantlyRNA = isPredominantlyRNA(nucleotideSequence,-1);
+ Sequence seq = new BasicSequence(SequenceType.NUCLEOTIDE, Taxon.getTaxon("x"), nucleotideSequence);
+ if( removeGaps ) {
+ seq = new GaplessSequence(seq);
+ }
+ int length=seq.getLength();
+ StringBuilder results =new StringBuilder();
+ for (int i = length-1; i >=0; i--) {
+ State state= seq.getState(i);
+ NucleotideState complementaryState = Nucleotides.COMPLEMENTARY_STATES[state.getIndex()];
+ if (predominantlyRNA && complementaryState.equals(Nucleotides.T_STATE)) {
+ results.append('U');
+ }
+ else {
+ results.append (complementaryState.getCode());
+ }
+ }
+ return results.toString();
+/*
+ State[] states = seq.getStates();
+ NucleotideState[] nucleotideStates = new NucleotideState[states.length];
+ for (int i = 0; i < states.length; i++) {
+ nucleotideStates[i] = (NucleotideState) states[i];
+ }
+ nucleotideStates = reverseComplement(nucleotideStates);
+ seq = new BasicSequence(SequenceType.NUCLEOTIDE, Taxon.getTaxon("x"), nucleotideStates);
+ return seq.getString();*/
+ }
+
+ /* kills gaps */
+ public static String reverseComplement(final String nucleotideSequence) {
+ return reverseComplement(nucleotideSequence, true);
+ }
+
+ public static String reverseComplementWithGaps(final String nucleotideSequence) {
+ return reverseComplement(nucleotideSequence, false);
+ }
+
+ /**
+ * Translates the given nucleotideSequence into an amino acid sequence string,
+ * using the given geneticCode. The translation is done triplet by triplet,
+ * starting with the triplet that is at index 0..2 in nucleotideSequence,
+ * then the one at index 3..5 etc. until there are less than 3 nucleotides
+ * left.
+ *
+ * This method uses {@link #translate(State[], GeneticCode)} to do the
+ * translation, hence it shares some properties with that method:
+ * 1.) Any excess nucleotides at the end will be silently discarded,
+ * 2.) Translation doesn't stop at stop codons; instead, they are
+ * translated to "*", which is {@link jebl.evolution.sequences.AminoAcids#STOP_STATE}'s code.
+ * @param nucleotideSequence nucleotide sequence string to translate
+ * @param geneticCode genetic code to use for the translation
+ * @return A string with length nucleotideSequence.length() / 3 (rounded down),
+ * the translation of <code>nucleotideSequence</code> with the given
+ * genetic code.
+ */
+ public static String translate(final String nucleotideSequence, GeneticCode geneticCode) {
+ Sequence seq = new BasicSequence(SequenceType.NUCLEOTIDE, Taxon.getTaxon("x"), nucleotideSequence);
+ seq = new GaplessSequence(seq);
+ State[] states = seq.getStates();
+
+ states = translate(states, geneticCode);
+ seq = new BasicSequence(SequenceType.AMINO_ACID, Taxon.getTaxon("x"), states);
+ return seq.getString();
+ }
+
+ public static State[] stripGaps(final State[] sequence) {
+ int count = 0;
+ for (State state : sequence) {
+ if (!state.isGap()) {
+ count++;
+ }
+ }
+
+ State[] stripped = new State[count];
+ int index = 0;
+ for (State state : sequence) {
+ if (!state.isGap()) {
+ stripped[index] = state;
+ index += 1;
+ }
+ }
+
+ return stripped;
+ }
+
+ public static State[] reverse(final State[] sequence) {
+ State[] reversed = new State[sequence.length];
+ for (int i = 0; i < sequence.length; i++) {
+ reversed[i] = sequence[sequence.length - i - 1];
+ }
+ return reversed;
+ }
+
+ public static NucleotideState[] complement(final NucleotideState[] sequence) {
+ NucleotideState[] complemented = new NucleotideState[sequence.length];
+ for (int i = 0; i < sequence.length; i++) {
+ complemented[i] = Nucleotides.COMPLEMENTARY_STATES[sequence[i].getIndex()];
+ }
+ return complemented;
+ }
+
+ public static NucleotideState[] reverseComplement(final NucleotideState[] sequence) {
+ NucleotideState[] reverseComplemented = new NucleotideState[sequence.length];
+ for (int i = 0; i < sequence.length; i++) {
+ reverseComplemented[i] = Nucleotides.COMPLEMENTARY_STATES[sequence[sequence.length - i - 1].getIndex()];
+ }
+ return reverseComplemented;
+ }
+
+ public static byte[] getStateIndices(final State[] sequence) {
+ byte[] indices = new byte[sequence.length];
+ int i = 0;
+ for (State state : sequence) {
+ indices[i] = (byte)state.getIndex();
+ }
+
+ return indices;
+ }
+
+ /**
+ * Gets the site location index for this sequence excluding
+ * any gaps. The location is indexed from 0.
+ * @param sequence the sequence
+ * @param gappedLocation the location including gaps
+ * @return the location without gaps.
+ */
+ public static int getGaplessLocation(Sequence sequence, int gappedLocation) {
+ int gapless = 0;
+ int gapped = 0;
+ for (State state : sequence.getStates()) {
+ if (gapped == gappedLocation) return gapless;
+ if (!state.isGap()) {
+ gapless ++;
+ }
+ gapped ++;
+ }
+ return gapless;
+ }
+
+ /**
+ * Gets the site location index for this sequence that corresponds
+ * to a location given excluding all gaps. The first non-gapped site
+ * in the sequence has a gaplessLocation of 0.
+ * @param sequence the sequence
+ * @param gaplessLocation
+ * @return the site location including gaps
+ */
+ public static int getGappedLocation(Sequence sequence, int gaplessLocation) {
+ int gapless = 0;
+ int gapped = 0;
+ for (State state : sequence.getStates()) {
+ if (gapless == gaplessLocation) return gapped;
+ if (!state.isGap()) {
+ gapless ++;
+ }
+ gapped ++;
+ }
+ return gapped;
+ }
+
+ /**
+ * Guess type of sequence from contents.
+ * @param seq the sequence
+ * @return SequenceType.NUCLEOTIDE or SequenceType.AMINO_ACID, if sequence is believed to be of that type.
+ * If the sequence contains characters that are valid for neither of these two sequence
+ * types, then this method returns null.
+ */
+ public static SequenceType guessSequenceType(final CharSequence seq) {
+
+ int canonicalNucStates = 0;
+ int undeterminedStates = 0;
+ // true length, excluding any gaps
+ int sequenceLength = seq.length();
+ final int seqLen = sequenceLength;
+
+ final int canonicalStateCount = Nucleotides.getCanonicalStateCount();
+
+ boolean onlyValidNucleotides = true;
+ boolean onlyValidAminoAcids = true;
+
+ // do not use toCharArray: it allocates an array size of sequence
+ for(int k = 0; (k < seqLen) && (onlyValidNucleotides || onlyValidAminoAcids); ++k) {
+ final char c = seq.charAt(k);
+
+ final NucleotideState nucState = Nucleotides.getState(c);
+ final boolean isNucState = nucState != null;
+ final boolean isAminoState = AminoAcids.getState(c) != null;
+
+ onlyValidNucleotides &= isNucState;
+ onlyValidAminoAcids &= isAminoState;
+
+ if (onlyValidNucleotides) {
+ assert(isNucState);
+ if (nucState.getIndex() < canonicalStateCount) {
+ ++canonicalNucStates;
+ } else {
+ if (nucState == Nucleotides.GAP_STATE) {
+ --sequenceLength;
+ } else if( nucState == Nucleotides.N_STATE ) {
+ ++undeterminedStates;
+ }
+ }
+ }
+ }
+
+ SequenceType result;
+ if (onlyValidNucleotides) { // only nucleotide states
+ // All sites are nucleotides (actual or ambigoues). If longer than 100 sites, declare it a nuc
+ if( sequenceLength >= 100 ) {
+ result = SequenceType.NUCLEOTIDE;
+ } else {
+ // if short, ask for 70% of ACGT or N
+ final double threshold = 0.7;
+ final int nucStates = canonicalNucStates + undeterminedStates;
+ // note: This implicitely assumes that every valid nucleotide
+ // symbol is also a valid amino acid. This is not true as of
+ // 2006-12-27, but will become true once we allow the 21st
+ // amino acid, U (Selenocysteine).
+ result = nucStates >= sequenceLength * threshold ? SequenceType.NUCLEOTIDE : SequenceType.AMINO_ACID;
+ }
+ } else if (onlyValidAminoAcids) {
+ result = SequenceType.AMINO_ACID;
+ } else {
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Produce a clean sequence filtered of spaces and digits.
+ * @param seq the sequence
+ * @param type the sequence type
+ * @return An array of valid states of SequenceType (may be shorter than the original sequence)
+ */
+ public static State[] cleanSequence(final CharSequence seq, final SequenceType type) {
+ int count = 0;
+ for (int i = 0; i < seq.length(); i++) {
+ final char c = seq.charAt(i);
+ if (type.getState(c) != null) {
+ count++;
+ }
+ }
+
+ State[] cleaned = new State[count];
+ int index = 0;
+ for (int i = 0; i < seq.length(); i++) {
+ final char c = seq.charAt(i);
+ State state = type.getState(c);
+ if (state != null) {
+ cleaned[index] = state;
+ index += 1;
+ }
+ }
+
+ return cleaned;
+ }
+
+ public static String toString(State[] states) {
+ StringBuilder builder = new StringBuilder();
+ for (State state : states) {
+ builder.append(state.getCode());
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/src/jebl/evolution/substmodel/AbstractRateMatrix.java b/src/jebl/evolution/substmodel/AbstractRateMatrix.java
new file mode 100644
index 0000000..79fc9e4
--- /dev/null
+++ b/src/jebl/evolution/substmodel/AbstractRateMatrix.java
@@ -0,0 +1,297 @@
+// RateMatrix.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.substmodel;
+
+import jebl.evolution.sequences.SequenceType;
+
+/**
+ * abstract base class for all rate matrices
+ *
+ * @version $Id: AbstractRateMatrix.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ * @author Alexei Drummond
+ */
+abstract public class AbstractRateMatrix implements RateMatrix
+{
+
+ //
+ // Public stuff
+ //
+
+ // Constraints and conventions:
+ // - first argument: row
+ // - second argument: column
+ // - transition: from row to column
+ // - sum of every row = 0
+ // - sum of frequencies = 1
+ // - frequencies * rate matrix = 0 (stationarity)
+ // - expected number of substitutions = 1 (Sum_i pi_i*R_ii = 0)
+
+ /** dimension */
+ private int dimension;
+
+ /** stationary frequencies (sum = 1.0) */
+ private double[] frequency;
+
+ /**
+ * rate matrix (transition: from 1st index to 2nd index)
+ */
+ private double[][] rate;
+
+ /** data type */
+ private SequenceType sequenceType;
+
+ //
+ // Private Stuff
+ //
+
+ private transient MatrixExponential matrixExp_;
+
+ /* The following is set to true in parameterChange(), and reset to false
+ * in setDistance()
+ */
+ private transient boolean rebuildModel_ = false;
+
+ private double[] parameterStore_ = null;
+
+ // Constructor
+ protected AbstractRateMatrix(int dim) {
+ dimension = dim;
+ frequency = new double[dim];
+ rate = new double[dim][dim];
+ scheduleRebuild();
+ }
+
+ private void scheduleRebuild() {
+ rebuildModel_ = true;
+ }
+
+ public int getDimension() { return dimension; }
+
+ /**
+ * @return stationary frequencies (sum = 1.0)
+ */
+ public double[] getEquilibriumFrequencies() { return frequency; }
+
+ /**
+ * @return stationary frequencie (sum = 1.0) for ith state
+ */
+ public double getEquilibriumFrequency(int i) { return frequency[i]; }
+
+ public SequenceType getSequenceType() { return sequenceType; }
+
+ protected final void setSequenceType(SequenceType dt) { this.sequenceType = dt; }
+
+ /**
+ * @return rate matrix (transition: from 1st index to 2nd index)
+ */
+ public double[][] getRelativeRates() { return rate; }
+
+ /**
+ * @return the probability of going from one state to another
+ * given the current distance
+ * @param fromState The state from which we are starting
+ * @param toState The resulting state
+ */
+ public double getTransitionProbability(int fromState, int toState) {
+ return matrixExp_.getTransitionProbability(fromState,toState);
+ }
+
+ private void handleRebuild() {
+ if(matrixExp_==null) {
+ matrixExp_ = new MatrixExponential(this);
+ }
+ if(rebuildModel_) {
+ rebuildRateMatrix(rate,parameterStore_);
+ fromQToR();
+ }
+ }
+
+ public final void rebuild() {}
+
+ /** Sets the distance (such as time/branch length) used when calculating
+ * the probabilities.
+ */
+ public final void setDistance(double distance) {
+ handleRebuild();
+ matrixExp_.setDistance(distance);
+ }
+
+ /** Sets the distance (such as time/branch length) used when calculating
+ * the probabilities. The resulting transition probabilities will be in reverse
+ * (that is in the matrix instead of [from][to] it's [to][from])
+ */
+ public final void setDistanceTranspose(double distance) {
+ handleRebuild();
+ matrixExp_.setDistanceTranspose(distance);
+ }
+
+ /** A utility method for speed, transfers trans prob information quickly
+ * into store
+ */
+ public final void getTransitionProbabilities(double[][] probabilityStore) {
+ matrixExp_.getTransitionProbabilities(probabilityStore);
+ }
+
+ public void scale(double scale) {
+ normalize(scale);
+ updateMatrixExp();
+ }
+
+ protected void setFrequencies(double[] f) {
+ System.arraycopy(f, 0, frequency, 0, dimension);
+ checkFrequencies();
+ scheduleRebuild();
+ }
+ public double setParametersNoScale(double[] parameters) {
+ rebuildRateMatrix(rate,parameters);
+ double result = incompleteFromQToR();
+ rebuildModel_ = false;
+ return result;
+ }
+
+ /** Computes normalized rate matrix from Q matrix (general reversible model)
+ * - Q_ii = 0
+ * - Q_ij = Q_ji
+ * - Q_ij is stored in R_ij (rate)
+ * - only upper triangular is used
+ * Also updates related MatrixExponential
+ */
+ private void fromQToR() {
+ double q;
+ for (int i = 0; i < dimension; i++) {
+ for (int j = i + 1; j < dimension; j++) {
+ q = rate[i][j];
+ rate[i][j] = q*frequency[j];
+ rate[j][i] = q*frequency[i];
+ }
+ }
+ makeValid();
+ normalize();
+ updateMatrixExp();
+ }
+
+ private double incompleteFromQToR() {
+ double q;
+ for (int i = 0; i < dimension; i++) {
+ for (int j = i + 1; j < dimension; j++) {
+ q = rate[i][j];
+ rate[i][j] = q*frequency[j];
+ rate[j][i] = q*frequency[i];
+ }
+ }
+ return makeValid();
+ }
+
+ abstract protected void rebuildRateMatrix(double[][] rate, double[] parameters);
+
+ protected void updateMatrixExp() {
+ if(matrixExp_==null) {
+ matrixExp_ = new MatrixExponential(this);
+ } else {
+ matrixExp_.setMatrix(this);
+ }
+ }
+ //
+ // Private stuff
+ //
+
+ /** Make it a valid rate matrix (make sum of rows = 0)
+ * @return current rate scale
+ */
+ private double makeValid() {
+ double total = 0;
+ for (int i = 0; i < dimension; i++){
+ double sum = 0.0;
+ for (int j = 0; j < dimension; j++)
+ {
+ if (i != j)
+ {
+ sum += rate[i][j];
+ }
+ }
+ rate[i][i] = -sum;
+ total+=frequency[i]*sum;
+ }
+ return total;
+ }
+
+ // Normalize rate matrix to one expected substitution per unit time
+ private void normalize()
+ {
+ double subst = 0.0;
+
+ for (int i = 0; i < dimension; i++)
+ {
+ subst += -rate[i][i]*frequency[i];
+ }
+ for (int i = 0; i < dimension; i++)
+ {
+ for (int j = 0; j < dimension; j++)
+ {
+ rate[i][j] = rate[i][j]/subst;
+ }
+ }
+ }
+ // Normalize rate matrix by a certain scale to acheive an overall scale (used with a complex site class model)
+ private void normalize(double substitutionScale) {
+ for (int i = 0; i < dimension; i++) {
+ for (int j = 0; j < dimension; j++) {
+ rate[i][j] = rate[i][j]/substitutionScale;
+ }
+ }
+ }
+
+ /**
+ * ensures that frequencies are not smaller than MINFREQ and
+ * that two frequencies differ by at least 2*MINFDIFF.
+ * This avoids potentiak problems later when eigenvalues
+ * are computed.
+ */
+ private void checkFrequencies()
+ {
+ // required frequency difference
+ double MINFDIFF = 1e-10;
+
+ // lower limit on frequency
+ double MINFREQ = 1e-10;
+
+ int maxi = 0;
+ double sum = 0.0;
+ double maxfreq = 0.0;
+ for (int i = 0; i < dimension; i++)
+ {
+ double freq = frequency[i];
+ if (freq < MINFREQ) frequency[i] = MINFREQ;
+ if (freq > maxfreq)
+ {
+ maxfreq = freq;
+ maxi = i;
+ }
+ sum += frequency[i];
+ }
+ frequency[maxi] += 1.0 - sum;
+
+ for (int i = 0; i < dimension - 1; i++)
+ {
+ for (int j = i+1; j < dimension; j++)
+ {
+ if (frequency[i] == frequency[j])
+ {
+ frequency[i] += MINFDIFF;
+ frequency[j] -= MINFDIFF;
+ }
+ }
+ }
+ }
+
+// ============================================================================
+// ==== Protected Stuff ==========
+ protected final double[] getFrequencies() { return frequency; }
+}
diff --git a/src/jebl/evolution/substmodel/AminoAcidModel.java b/src/jebl/evolution/substmodel/AminoAcidModel.java
new file mode 100644
index 0000000..969254d
--- /dev/null
+++ b/src/jebl/evolution/substmodel/AminoAcidModel.java
@@ -0,0 +1,36 @@
+// AminoAcidModel.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.substmodel;
+
+import jebl.evolution.sequences.SequenceType;
+
+
+/**
+ * base class of rate matrices for amino acids
+ *
+ * @version $Id: AminoAcidModel.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ */
+public abstract class AminoAcidModel extends AbstractRateMatrix
+{
+
+ //
+ // Protected stuff
+ //
+
+ // Constructor
+ protected AminoAcidModel(double[] f)
+ {
+ // Dimension = 20
+ super(20);
+
+ setSequenceType(SequenceType.AMINO_ACID);
+ setFrequencies(f);
+ }
+}
diff --git a/src/jebl/evolution/substmodel/MatrixExponential.java b/src/jebl/evolution/substmodel/MatrixExponential.java
new file mode 100644
index 0000000..bb4644a
--- /dev/null
+++ b/src/jebl/evolution/substmodel/MatrixExponential.java
@@ -0,0 +1,912 @@
+// MatrixExponential.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.substmodel;
+
+import jebl.math.MachineAccuracy;
+
+/**
+ * compute matrix exponential and, subsequently, transition probabilities
+ * for a given rate matrix
+ *
+ * @version $Id: MatrixExponential.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ */
+public class MatrixExponential implements Cloneable, java.io.Serializable
+{
+ //
+ // Public stuff
+ //
+
+ // Constraints and conventions:
+ // - first argument: row
+ // - second argument: column
+ // - transition: from row to column
+ // - sum of every row = 1
+ // - sum of frequencies = 1
+ // - frequencies * rate matrix = 1 (stationarity)
+//
+ // Private stuff
+ //
+
+ // Eigenvalues, eigenvectors, and inverse eigenvectors
+ private final double[] Eval;
+ private final double[][] Evec;
+ private final double[][] Ievc;
+ private final double[][] iexp;
+ private final int[] ordr;
+ private final double[] evali;
+ private final double amat[][];
+
+
+ /** dimension of rate matrix */
+ private final int dimension_;
+
+ /** transition probability matrix */
+ private final double[][] transProb;
+
+
+
+ /**
+ */
+ public MatrixExponential(int dimension) {
+ this.dimension_ = dimension;
+ if(dimension<=0) {
+ throw new IllegalArgumentException("Invalid dimension:"+dimension);
+ }
+ transProb = new double[dimension][dimension];
+ Eval = new double[dimension];
+ Evec = new double[dimension][dimension];
+ Ievc = new double[dimension][dimension];
+ iexp = new double[dimension][dimension];
+ amat = new double[dimension][dimension];
+
+ ordr = new int[dimension];
+ evali = new double[dimension];
+
+ }
+ /**
+ * create module
+ *
+ * @param r rate matrix
+ */
+ public MatrixExponential(RateMatrix r) {
+ int dimension = r.getDimension();
+ this.dimension_ = dimension;
+ transProb = new double[dimension][dimension];
+ Eval = new double[dimension];
+ Evec = new double[dimension][dimension];
+ Ievc = new double[dimension][dimension];
+ iexp = new double[dimension][dimension];
+ amat = new double[dimension][dimension];
+
+ ordr = new int[dimension];
+ evali = new double[dimension];
+ setMatrix(r);
+ }
+
+ public final double getTransitionProbability(int from, int to) {
+ return transProb[from][to];
+ }
+
+ public int getDimension() { return dimension_; }
+
+ public void updateByRelativeRates(double[][] relativeRates) {
+ /* compute eigenvalues and eigenvectors */
+ for (int i = 0; i < dimension_; i++) {
+ System.arraycopy(relativeRates[i], 0, amat[i], 0, dimension_);
+ }
+
+ elmhes(amat, ordr, dimension_);
+ eltran(amat, Evec, ordr, dimension_);
+ hqr2(dimension_, 1, dimension_, amat, Evec, Eval, evali);
+ luinverse(Evec, Ievc, dimension_);
+ }
+
+ /**
+ * update rate matrix used in present module
+ *
+ * @param r rate matrix
+ */
+ public void setMatrix(RateMatrix r) {
+ updateByRelativeRates(r.getRelativeRates());
+ }
+
+ /**
+ * A utility method for speed, transfers trans prob information quickly
+ * into store
+ */
+ public final void getTransitionProbabilities(double[][] probabilityStore) {
+ // this check should be changed to an assertion once we adopt Java 1.4
+ if (probabilityStore == null) throw new RuntimeException("Assertion Error: probabilityStore == null");
+ jebl.util.Utils.copy(transProb,probabilityStore);
+ }
+
+ /**
+ * compute transition probabilities for a expected distance
+ * using the prespecified rate matrix
+ *
+ * @param arc expected distance
+ */
+ public final void setDistance(double arc) {
+
+ int i, j, k;
+ double temp;
+ for (k = 0; k < dimension_; k++) {
+ temp = Math.exp(arc * Eval[k]);
+ for (j = 0; j < dimension_; j++) {
+ iexp[k][j] = Ievc[k][j] * temp;
+ }
+ }
+ for (i = 0; i < dimension_; i++) {
+ for (j = 0; j < dimension_; j++) {
+ temp = 0.0;
+ for (k = 0; k < dimension_; k++) {
+ temp += Evec[i][k] * iexp[k][j];
+ }
+ transProb[i][j] = Math.abs(temp);
+ }
+ }
+ }
+
+ /**
+ * compute transition probabilities for a expected distance
+ * using the prespecified rate matrix. The resulting matrix
+ * works [to][from] as opposed to [from][to]
+ *
+ * @param arc expected distance
+ */
+ public final void setDistanceTranspose(double arc) {
+
+ int i, j, k;
+ double temp;
+ for (k = 0; k < dimension_ ; k++) {
+ temp = Math.exp(arc * Eval[k]);
+ for (j = 0; j < dimension_ ; j++) {
+ iexp[k][j] = Ievc[k][j] * temp;
+ }
+ }
+ for (i = 0; i < dimension_ ; i++) {
+ for (j = 0; j < dimension_ ; j++) {
+ temp = 0.0;
+ for (k = 0; k < dimension_ ; k++) {
+ temp += Evec[i][k] * iexp[k][j];
+ }
+ //This is the line that is different from the normal setDistance method
+ //Was transProb[j][i]
+ transProb[j][i] = Math.abs(temp);
+ }
+ }
+ }
+
+
+ private void elmhes(double[][] a, int[] ordr, int n) {
+ int m, j, i;
+ double y, x;
+
+ for (i = 0; i < n; i++) {
+ ordr[i] = 0;
+ }
+ for (m = 2; m < n; m++) {
+ x = 0.0;
+ i = m;
+ for (j = m; j <= n; j++) {
+ if (Math.abs(a[j - 1][m - 2]) > Math.abs(x)) {
+ x = a[j - 1][m - 2];
+ i = j;
+ }
+ }
+ ordr[m - 1] = i;
+ if (i != m) {
+ for (j = m - 2; j < n; j++) {
+ y = a[i - 1][j];
+ a[i - 1][j] = a[m - 1][j];
+ a[m - 1][j] = y;
+ }
+ for (j = 0; j < n; j++) {
+ y = a[j][i - 1];
+ a[j][i - 1] = a[j][m - 1];
+ a[j][m - 1] = y;
+ }
+ }
+ if (x != 0.0) {
+ for (i = m; i < n; i++) {
+ y = a[i][m - 2];
+ if (y != 0.0){
+ y /= x;
+ a[i][m - 2] = y;
+ for (j = m - 1; j < n; j++)
+ {
+ a[i][j] -= y * a[m - 1][j];
+ }
+ for (j = 0; j < n; j++)
+ {
+ a[j][m - 1] += y * a[j][i];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Helper variables for mcdiv
+ private double cr, ci;
+
+ private void mcdiv(double ar, double ai, double br, double bi)
+ {
+ double s, ars, ais, brs, bis;
+
+ s = Math.abs(br) + Math.abs(bi);
+ ars = ar/s;
+ ais = ai/s;
+ brs = br/s;
+ bis = bi/s;
+ s = brs * brs + bis * bis;
+ cr = (ars * brs + ais * bis)/s;
+ ci = (ais * brs - ars * bis)/s;
+ }
+
+ void hqr2(int n, int low, int hgh, double[][] h, double[][] zz,
+ double[] wr, double[] wi) throws ArithmeticException
+ {
+ int i, j, k, l=0, m, en, na, itn, its;
+ double p=0, q=0, r=0, s=0, t, w, x=0, y, ra, sa, vi, vr, z=0, norm, tst1, tst2;
+ boolean notLast;
+
+
+ norm = 0.0;
+ k = 1;
+ /* store isolated roots and compute matrix norm */
+ for (i = 0; i < n; i++)
+ {
+ for (j = k - 1; j < n; j++)
+ {
+ norm += Math.abs(h[i][j]);
+ }
+ k = i + 1;
+ if (i + 1 < low || i + 1 > hgh)
+ {
+ wr[i] = h[i][i];
+ wi[i] = 0.0;
+ }
+ }
+ en = hgh;
+ t = 0.0;
+ itn = n * 30;
+ while (en >= low)
+ { /* search for next eigenvalues */
+ its = 0;
+ na = en - 1;
+ while (en >= 1)
+ {
+ /* look for single small sub-diagonal element */
+ boolean fullLoop = true;
+ for (l = en; l > low; l--)
+ {
+ s = Math.abs(h[l - 2][l - 2]) + Math.abs(h[l - 1][l - 1]);
+ if (s == 0.0)
+ {
+ s = norm;
+ }
+ tst1 = s;
+ tst2 = tst1 + Math.abs(h[l - 1][l - 2]);
+ if (tst2 == tst1)
+ {
+ fullLoop = false;
+ break;
+ }
+ }
+ if (fullLoop)
+ {
+ l = low;
+ }
+
+ x = h[en - 1][en - 1]; /* form shift */
+ if (l == en || l == na)
+ {
+ break;
+ }
+ if (itn == 0)
+ {
+ /* eigenvalues have not converged */
+ System.out.println("Eigenvalues not converged");
+ throw new ArithmeticException();
+ }
+ y = h[na - 1][na - 1];
+ w = h[en - 1][na - 1] * h[na - 1][en - 1];
+ /* form exceptional shift */
+ if (its == 10 || its == 20)
+ {
+ t += x;
+ for (i = low - 1; i < en; i++)
+ {
+ h[i][i] -= x;
+ }
+ s = Math.abs(h[en - 1][na - 1]) + Math.abs(h[na - 1][en - 3]);
+ x = 0.75 * s;
+ y = x;
+ w = -0.4375 * s * s;
+ }
+ its++;
+ itn--;
+ /* look for two consecutive small sub-diagonal elements */
+ for (m = en - 2; m >= l; m--)
+ {
+ z = h[m - 1][m - 1];
+ r = x - z;
+ s = y - z;
+ p = (r * s - w) / h[m][m - 1] + h[m - 1][m];
+ q = h[m][m] - z - r - s;
+ r = h[m + 1][m];
+ s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+ p /= s;
+ q /= s;
+ r /= s;
+ if (m == l)
+ {
+ break;
+ }
+ tst1 = Math.abs(p) * (Math.abs(h[m - 2][m - 2]) + Math.abs(z) + Math.abs(h[m][m]));
+ tst2 = tst1 + Math.abs(h[m - 1][m - 2]) * (Math.abs(q) + Math.abs(r));
+ if (tst2 == tst1)
+ {
+ break;
+ }
+ }
+ for (i = m + 2; i <= en; i++)
+ {
+ h[i - 1][i - 3] = 0.0;
+ if (i != m + 2)
+ {
+ h[i - 1][i - 4] = 0.0;
+ }
+ }
+ for (k = m; k <= na; k++)
+ {
+ notLast = k != na;
+ if (k != m)
+ {
+ p = h[k - 1][k - 2];
+ q = h[k][k - 2];
+ r = 0.0;
+ if (notLast)
+ {
+ r = h[k + 1][k - 2];
+ }
+ x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+ if (x != 0.0)
+ {
+ p /= x;
+ q /= x;
+ r /= x;
+ }
+ }
+ if (x != 0.0)
+ {
+ if (p < 0.0)
+ { /* sign */
+ s = - Math.sqrt(p * p + q * q + r * r);
+ }
+ else
+ {
+ s = Math.sqrt(p * p + q * q + r * r);
+ }
+ if (k != m)
+ {
+ h[k - 1][k - 2] = -s * x;
+ }
+ else if (l != m)
+ {
+ h[k - 1][k - 2] = -h[k - 1][k - 2];
+ }
+ p += s;
+ x = p / s;
+ y = q / s;
+ z = r / s;
+ q /= p;
+ r /= p;
+ if (!notLast)
+ {
+ for (j = k - 1; j < n; j++)
+ { /* row modification */
+ p = h[k - 1][j] + q * h[k][j];
+ h[k - 1][j] -= p * x;
+ h[k][j] -= p * y;
+ }
+ j = (en < (k + 3)) ? en : (k + 3); /* min */
+ for (i = 0; i < j; i++)
+ { /* column modification */
+ p = x * h[i][k - 1] + y * h[i][k];
+ h[i][k - 1] -= p;
+ h[i][k] -= p * q;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++)
+ {
+ p = x * zz[i][k - 1] + y * zz[i][k];
+ zz[i][k - 1] -= p;
+ zz[i][k] -= p * q;
+ }
+ }
+ else
+ {
+ for (j = k - 1; j < n; j++)
+ { /* row modification */
+ p = h[k - 1][j] + q * h[k][j] + r * h[k + 1][j];
+ h[k - 1][j] -= p * x;
+ h[k][j] -= p * y;
+ h[k + 1][j] -= p * z;
+ }
+ j = (en < (k + 3)) ? en : (k + 3); /* min */
+ for (i = 0; i < j; i++)
+ { /* column modification */
+ p = x * h[i][k - 1] + y * h[i][k] + z * h[i][k + 1];
+ h[i][k - 1] -= p;
+ h[i][k] -= p * q;
+ h[i][k + 1] -= p * r;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++)
+ {
+ p = x * zz[i][k - 1] + y * zz[i][k] +
+ z * zz[i][k + 1];
+ zz[i][k - 1] -= p;
+ zz[i][k] -= p * q;
+ zz[i][k + 1] -= p * r;
+ }
+ }
+ }
+ } /* for k */
+ } /* while infinite loop */
+ if (l == en)
+ { /* one root found */
+ h[en - 1][en - 1] = x + t;
+ wr[en - 1] = h[en - 1][en - 1];
+ wi[en - 1] = 0.0;
+ en = na;
+ continue;
+ }
+ y = h[na - 1][na - 1];
+ w = h[en - 1][na - 1] * h[na - 1][en - 1];
+ p = (y - x) / 2.0;
+ q = p * p + w;
+ z = Math.sqrt(Math.abs(q));
+ h[en - 1][en - 1] = x + t;
+ x = h[en - 1][en - 1];
+ h[na - 1][na - 1] = y + t;
+ if (q >= 0.0)
+ { /* real pair */
+ if (p < 0.0)
+ { /* sign */
+ z = p - Math.abs(z);
+ }
+ else
+ {
+ z = p + Math.abs(z);
+ }
+ wr[na - 1] = x + z;
+ wr[en - 1] = wr[na - 1];
+ if (z != 0.0)
+ {
+ wr[en - 1] = x - w / z;
+ }
+ wi[na - 1] = 0.0;
+ wi[en - 1] = 0.0;
+ x = h[en - 1][na - 1];
+ s = Math.abs(x) + Math.abs(z);
+ p = x / s;
+ q = z / s;
+ r = Math.sqrt(p * p + q * q);
+ p /= r;
+ q /= r;
+ for (j = na - 1; j < n; j++)
+ { /* row modification */
+ z = h[na - 1][j];
+ h[na - 1][j] = q * z + p * h[en - 1][j];
+ h[en - 1][j] = q * h[en - 1][j] - p * z;
+ }
+ for (i = 0; i < en; i++)
+ { /* column modification */
+ z = h[i][na - 1];
+ h[i][na - 1] = q * z + p * h[i][en - 1];
+ h[i][en - 1] = q * h[i][en - 1] - p * z;
+ }
+ /* accumulate transformations */
+ for (i = low - 1; i < hgh; i++)
+ {
+ z = zz[i][na - 1];
+ zz[i][na - 1] = q * z + p * zz[i][en - 1];
+ zz[i][en - 1] = q * zz[i][en - 1] - p * z;
+ }
+ }
+ else
+ { /* complex pair */
+ wr[na - 1] = x + p;
+ wr[en - 1] = x + p;
+ wi[na - 1] = z;
+ wi[en - 1] = -z;
+ }
+ en -= 2;
+ } /* while en >= low */
+ /* backsubstitute to find vectors of upper triangular form */
+ if (norm != 0.0)
+ {
+ for (en = n; en >= 1; en--)
+ {
+ p = wr[en - 1];
+ q = wi[en - 1];
+ na = en - 1;
+ if (q == 0.0)
+ {/* real vector */
+ m = en;
+ h[en - 1][en - 1] = 1.0;
+ if (na != 0)
+ {
+ for (i = en - 2; i >= 0; i--)
+ {
+ w = h[i][i] - p;
+ r = 0.0;
+ for (j = m - 1; j < en; j++)
+ {
+ r += h[i][j] * h[j][en - 1];
+ }
+ if (wi[i] < 0.0)
+ {
+ z = w;
+ s = r;
+ }
+ else
+ {
+ m = i + 1;
+ if (wi[i] == 0.0)
+ {
+ t = w;
+ if (t == 0.0)
+ {
+ tst1 = norm;
+ t = tst1;
+ do
+ {
+ t = 0.01 * t;
+ tst2 = norm + t;
+ }
+ while (tst2 > tst1);
+ }
+ h[i][en - 1] = -(r / t);
+ }
+ else
+ { /* solve real equations */
+ x = h[i][i + 1];
+ y = h[i + 1][i];
+ q = (wr[i] - p) * (wr[i] - p) + wi[i] * wi[i];
+ t = (x * s - z * r) / q;
+ h[i][en - 1] = t;
+ if (Math.abs(x) > Math.abs(z))
+ h[i + 1][en - 1] = (-r - w * t) / x;
+ else
+ h[i + 1][en - 1] = (-s - y * t) / z;
+ }
+ /* overflow control */
+ t = Math.abs(h[i][en - 1]);
+ if (t != 0.0)
+ {
+ tst1 = t;
+ tst2 = tst1 + 1.0 / tst1;
+ if (tst2 <= tst1)
+ {
+ for (j = i; j < en; j++)
+ {
+ h[j][en - 1] /= t;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (q > 0.0)
+ {
+ m = na;
+ if (Math.abs(h[en - 1][na - 1]) > Math.abs(h[na - 1][en - 1]))
+ {
+ h[na - 1][na - 1] = q / h[en - 1][na - 1];
+ h[na - 1][en - 1] = (p - h[en - 1][en - 1])/h[en - 1][na - 1];
+ }
+ else
+ {
+ mcdiv(0.0, -h[na - 1][en - 1], h[na - 1][na - 1] - p, q);
+ h[na - 1][na - 1] = cr;
+ h[na - 1][en - 1] = ci;
+ }
+ h[en - 1][na - 1] = 0.0;
+ h[en - 1][en - 1] = 1.0;
+ if (en != 2)
+ {
+ for (i = en - 3; i >= 0; i--)
+ {
+ w = h[i][i] - p;
+ ra = 0.0;
+ sa = 0.0;
+ for (j = m - 1; j < en; j++)
+ {
+ ra += h[i][j] * h[j][na - 1];
+ sa += h[i][j] * h[j][en - 1];
+ }
+ if (wi[i] < 0.0)
+ {
+ z = w;
+ r = ra;
+ s = sa;
+ }
+ else
+ {
+ m = i + 1;
+ if (wi[i] == 0.0)
+ {
+ mcdiv(-ra, -sa, w, q);
+ h[i][na - 1] = cr;
+ h[i][en - 1] = ci;
+ }
+ else
+ { /* solve complex equations */
+ x = h[i][i + 1];
+ y = h[i + 1][i];
+ vr = (wr[i] - p) * (wr[i] - p);
+ vr = vr + wi[i] * wi[i] - q * q;
+ vi = (wr[i] - p) * 2.0 * q;
+ if (vr == 0.0 && vi == 0.0)
+ {
+ tst1 = norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) +
+ Math.abs(y) + Math.abs(z));
+ vr = tst1;
+ do
+ {
+ vr = 0.01 * vr;
+ tst2 = tst1 + vr;
+ }
+ while (tst2 > tst1);
+ }
+ mcdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
+ h[i][na - 1] = cr;
+ h[i][en - 1] = ci;
+ if (Math.abs(x) > Math.abs(z) + Math.abs(q))
+ {
+ h[i + 1]
+ [na - 1] = (q * h[i][en - 1] -
+ w * h[i][na - 1] - ra) / x;
+ h[i + 1][en - 1] = (-sa - w * h[i][en - 1] -
+ q * h[i][na - 1]) / x;
+ }
+ else
+ {
+ mcdiv(-r - y * h[i][na - 1], -s - y * h[i][en - 1], z, q);
+ h[i + 1][na - 1] = cr;
+ h[i + 1][en - 1] = ci;
+ }
+ }
+ /* overflow control */
+ t = (Math.abs(h[i][na - 1]) > Math.abs(h[i][en - 1])) ?
+ Math.abs(h[i][na - 1]) : Math.abs(h[i][en - 1]);
+ if (t != 0.0)
+ {
+ tst1 = t;
+ tst2 = tst1 + 1.0 / tst1;
+ if (tst2 <= tst1)
+ {
+ for (j = i; j < en; j++)
+ {
+ h[j][na - 1] /= t;
+ h[j][en - 1] /= t;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ /* end back substitution. vectors of isolated roots */
+ for (i = 0; i < n; i++)
+ {
+ if (i + 1 < low || i + 1 > hgh)
+ {
+ for (j = i; j < n; j++)
+ {
+ zz[i][j] = h[i][j];
+ }
+ }
+ }
+ /* multiply by transformation matrix to give vectors of
+ * original full matrix. */
+ for (j = n - 1; j >= low - 1; j--)
+ {
+ m = ((j + 1) < hgh) ? (j + 1) : hgh; /* min */
+ for (i = low - 1; i < hgh; i++)
+ {
+ z = 0.0;
+ for (k = low - 1; k < m; k++)
+ {
+ z += zz[i][k] * h[k][j];
+ }
+ zz[i][j] = z;
+ }
+ }
+ }
+ }
+
+ private void eltran(double[][] a, double[][] zz, int[] ordr, int n)
+ {
+ int i, j, m;
+
+ for (i = 0; i < n; i++)
+ {
+ for (j = i + 1; j < n; j++)
+ {
+ zz[i][j] = 0.0;
+ zz[j][i] = 0.0;
+ }
+ zz[i][i] = 1.0;
+ }
+ if (n <= 2)
+ {
+ return;
+ }
+ for (m = n - 1; m >= 2; m--)
+ {
+ for (i = m; i < n; i++)
+ {
+ zz[i][m - 1] = a[i][m - 2];
+ }
+ i = ordr[m - 1];
+ if (i != m)
+ {
+ for (j = m - 1; j < n; j++)
+ {
+ zz[m - 1][j] = zz[i - 1][j];
+ zz[i - 1][j] = 0.0;
+ }
+ zz[i - 1][m - 1] = 1.0;
+ }
+ }
+ }
+
+ void luinverse(double[][] inmat, double[][] imtrx, int size) throws IllegalArgumentException
+ {
+ int i, j, k, l, maxi=0, idx, ix, jx;
+ double sum, tmp, maxb, aw;
+ int[] index;
+ double[] wk;
+ double[][] omtrx;
+
+
+ index = new int[size];
+ omtrx = new double[size][size];
+
+ /* copy inmat to omtrx */
+ for (i = 0; i < size; i++)
+ {
+ for (j = 0; j < size; j++)
+ {
+ omtrx[i][j] = inmat[i][j];
+ }
+ }
+
+ wk = new double[size];
+ aw = 1.0;
+ for (i = 0; i < size; i++)
+ {
+ maxb = 0.0;
+ for (j = 0; j < size; j++)
+ {
+ if (Math.abs(omtrx[i][j]) > maxb)
+ {
+ maxb = Math.abs(omtrx[i][j]);
+ }
+ }
+ if (maxb == 0.0)
+ {
+ /* Singular matrix */
+ System.out.println("Singular matrix encountered");
+ throw new IllegalArgumentException("Singular matrix");
+ }
+ wk[i] = 1.0/maxb;
+ }
+ for (j = 0; j < size; j++)
+ {
+ for (i = 0; i < j; i++)
+ {
+ sum = omtrx[i][j];
+ for (k = 0; k < i; k++)
+ {
+ sum -= omtrx[i][k] * omtrx[k][j];
+ }
+ omtrx[i][j] = sum;
+ }
+ maxb = 0.0;
+ for (i = j; i < size; i++)
+ {
+ sum = omtrx[i][j];
+ for (k = 0; k < j; k++)
+ {
+ sum -= omtrx[i][k] * omtrx[k][j];
+ }
+ omtrx[i][j] = sum;
+ tmp = wk[i] * Math.abs(sum);
+ if (tmp >= maxb)
+ {
+ maxb = tmp;
+ maxi = i;
+ }
+ }
+ if (j != maxi)
+ {
+ for (k = 0; k < size; k++)
+ {
+ tmp = omtrx[maxi][k];
+ omtrx[maxi][k] = omtrx[j][k];
+ omtrx[j][k] = tmp;
+ }
+ aw = -aw;
+ wk[maxi] = wk[j];
+ }
+ index[j] = maxi;
+ if (omtrx[j][j] == 0.0)
+ {
+ omtrx[j][j] = MachineAccuracy.EPSILON;
+ }
+ if (j != size - 1)
+ {
+ tmp = 1.0 / omtrx[j][j];
+ for (i = j + 1; i < size; i++)
+ {
+ omtrx[i][j] *= tmp;
+ }
+ }
+ }
+ for (jx = 0; jx < size; jx++)
+ {
+ for (ix = 0; ix < size; ix++)
+ {
+ wk[ix] = 0.0;
+ }
+ wk[jx] = 1.0;
+ l = -1;
+ for (i = 0; i < size; i++)
+ {
+ idx = index[i];
+ sum = wk[idx];
+ wk[idx] = wk[i];
+ if (l != -1)
+ {
+ for (j = l; j < i; j++)
+ {
+ sum -= omtrx[i][j] * wk[j];
+ }
+ }
+ else if (sum != 0.0)
+ {
+ l = i;
+ }
+ wk[i] = sum;
+ }
+ for (i = size - 1; i >= 0; i--)
+ {
+ sum = wk[i];
+ for (j = i + 1; j < size; j++)
+ {
+ sum -= omtrx[i][j] * wk[j];
+ }
+ wk[i] = sum/omtrx[i][i];
+ }
+ for (ix = 0; ix < size; ix++)
+ {
+ imtrx[ix][jx] = wk[ix];
+ }
+ }
+ }
+}
diff --git a/src/jebl/evolution/substmodel/RateMatrix.java b/src/jebl/evolution/substmodel/RateMatrix.java
new file mode 100644
index 0000000..b1cf5ba
--- /dev/null
+++ b/src/jebl/evolution/substmodel/RateMatrix.java
@@ -0,0 +1,74 @@
+// RateMatrix.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.substmodel;
+
+import jebl.evolution.sequences.SequenceType;
+
+import java.io.Serializable;
+
+/**
+ * abstract base class for all rate matrices
+ *
+ * @version $Id: RateMatrix.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ * @author Alexei Drummond
+ * @author Matthew Goode
+ */
+public interface RateMatrix extends Cloneable, Serializable {
+
+ /**
+ * @return a short unique human-readable identifier for this rate matrix.
+ */
+ String getUniqueName();
+
+ /**
+ * @return the dimension of this rate matrix.
+ */
+ int getDimension();
+
+ /**
+ * @return stationary frequencies (sum = 1.0)
+ */
+ double[] getEquilibriumFrequencies();
+
+ /**
+ * @return stationary frequency (sum = 1.0) for ith state
+ * Preferred method for infrequent use.
+ */
+ double getEquilibriumFrequency(int i);
+
+ /**
+ * Get the data type of this rate matrix
+ */
+ SequenceType getSequenceType();
+
+ /**
+ * @return rate matrix (transition: from 1st index to 2nd index)
+ */
+ double[][] getRelativeRates();
+
+ /**
+ * @return the probability of going from one state to another
+ * given the current distance
+ * @param fromState The state from which we are starting
+ * @param toState The resulting state
+ */
+ double getTransitionProbability(int fromState, int toState);
+
+ /**
+ * A utility method for speed, transfers trans prob information quickly
+ * into store.
+ */
+ void getTransitionProbabilities(double[][] probabilityStore);
+
+ /** Sets the distance (such as time/branch length) used when calculating
+ * the probabilities. This method may well take the most time!
+ */
+ void setDistance(double distance);
+}
diff --git a/src/jebl/evolution/substmodel/WAG.java b/src/jebl/evolution/substmodel/WAG.java
new file mode 100644
index 0000000..885dbaf
--- /dev/null
+++ b/src/jebl/evolution/substmodel/WAG.java
@@ -0,0 +1,202 @@
+// WAG.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.evolution.substmodel;
+
+/**
+ * WAG model of amino acid evolution (S. Whelan and N. Goldman 2000)
+ *
+ * @version $Id: WAG.java 185 2006-01-23 23:03:18Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ * @author Alexei Drummond
+ */
+public class WAG extends AminoAcidModel
+{
+ /**
+ * constructor
+ *
+ * @param f amino acid frequencies
+ */
+ public WAG(double[] f)
+ {
+ super(f);
+
+ }
+
+ /**
+ * get the frequencies of the original data set that
+ * formed the basis for the estimation of the rate matrix
+ *
+ * @param f array where amino acid frequencies will be stored
+ */
+ public static void getOriginalFrequencies(double[] f)
+ {
+ f[0] = 0.0866;
+ f[1] = 0.0440;
+ f[2] = 0.0391;
+ f[3] = 0.0570;
+ f[4] = 0.0193;
+ f[5] = 0.0367;
+ f[6] = 0.0581;
+ f[7] = 0.0833;
+ f[8] = 0.0244;
+ f[9] = 0.0485;
+ f[10] = 0.0862;
+ f[11] = 0.0620;
+ f[12] = 0.0195;
+ f[13] = 0.0384;
+ f[14] = 0.0458;
+ f[15] = 0.0695;
+ f[16] = 0.0610;
+ f[17] = 0.0144;
+ f[18] = 0.0353;
+ f[19] = 0.0709;
+ }
+
+ /**
+ * @return the frequencies of the original data set that
+ * formed the basis for the estimation of the rate matrix
+ */
+ public static double[] getOriginalFrequencies() {
+ double[] f = new double[20];
+ getOriginalFrequencies(f);
+ return f;
+ }
+
+ //
+ // Private stuff
+ //
+
+ // WAG model of amino acid evolution
+ // Whelan, S. and N. Goldman. 2000. in prep.
+ protected void rebuildRateMatrix(double[][] rate, double[] parameters) {
+ // Q matrix
+ rate[0][1] = 0.610810; rate[0][2] = 0.569079;
+ rate[0][3] = 0.821500; rate[0][4] = 1.141050;
+ rate[0][5] = 1.011980; rate[0][6] = 1.756410;
+ rate[0][7] = 1.572160; rate[0][8] = 0.354813;
+ rate[0][9] = 0.219023; rate[0][10] = 0.443935;
+ rate[0][11] = 1.005440; rate[0][12] = 0.989475;
+ rate[0][13] = 0.233492; rate[0][14] = 1.594890;
+ rate[0][15] = 3.733380; rate[0][16] = 2.349220;
+ rate[0][17] = 0.125227; rate[0][18] = 0.268987;
+ rate[0][19] = 2.221870;
+
+ rate[1][2] = 0.711690; rate[1][3] = 0.165074;
+ rate[1][4] = 0.585809; rate[1][5] = 3.360330;
+ rate[1][6] = 0.488649; rate[1][7] = 0.650469;
+ rate[1][8] = 2.362040; rate[1][9] = 0.206722;
+ rate[1][10] = 0.551450; rate[1][11] = 5.925170;
+ rate[1][12] = 0.758446; rate[1][13] = 0.116821;
+ rate[1][14] = 0.753467; rate[1][15] = 1.357640;
+ rate[1][16] = 0.613776; rate[1][17] = 1.294610;
+ rate[1][18] = 0.423612; rate[1][19] = 0.280336;
+
+ rate[2][3] = 6.013660; rate[2][4] = 0.296524;
+ rate[2][5] = 1.716740; rate[2][6] = 1.056790;
+ rate[2][7] = 1.253910; rate[2][8] = 4.378930;
+ rate[2][9] = 0.615636; rate[2][10] = 0.147156;
+ rate[2][11] = 3.334390; rate[2][12] = 0.224747;
+ rate[2][13] = 0.110793; rate[2][14] = 0.217538;
+ rate[2][15] = 4.394450; rate[2][16] = 2.257930;
+ rate[2][17] = 0.078463; rate[2][18] = 1.208560;
+ rate[2][19] = 0.221176;
+
+ rate[3][4] = 0.033379; rate[3][5] = 0.691268;
+ rate[3][6] = 6.833400; rate[3][7] = 0.961142;
+ rate[3][8] = 1.032910; rate[3][9] = 0.043523;
+ rate[3][10] = 0.093930; rate[3][11] = 0.533362;
+ rate[3][12] = 0.116813; rate[3][13] = 0.052004;
+ rate[3][14] = 0.472601; rate[3][15] = 1.192810;
+ rate[3][16] = 0.417372; rate[3][17] = 0.146348;
+ rate[3][18] = 0.363243; rate[3][19] = 0.169417;
+
+ rate[4][5] = 0.109261; rate[4][6] = 0.023920;
+ rate[4][7] = 0.341086; rate[4][8] = 0.275403;
+ rate[4][9] = 0.189890; rate[4][10] = 0.428414;
+ rate[4][11] = 0.083649; rate[4][12] = 0.437393;
+ rate[4][13] = 0.441300; rate[4][14] = 0.122303;
+ rate[4][15] = 1.560590; rate[4][16] = 0.570186;
+ rate[4][17] = 0.795736; rate[4][18] = 0.604634;
+ rate[4][19] = 1.114570;
+
+ rate[5][6] = 6.048790; rate[5][7] = 0.366510;
+ rate[5][8] = 4.749460; rate[5][9] = 0.131046;
+ rate[5][10] = 0.964886; rate[5][11] = 4.308310;
+ rate[5][12] = 1.705070; rate[5][13] = 0.110744;
+ rate[5][14] = 1.036370; rate[5][15] = 1.141210;
+ rate[5][16] = 0.954144; rate[5][17] = 0.243615;
+ rate[5][18] = 0.252457; rate[5][19] = 0.333890;
+
+ rate[6][7] = 0.630832; rate[6][8] = 0.635025;
+ rate[6][9] = 0.141320; rate[6][10] = 0.172579;
+ rate[6][11] = 2.867580; rate[6][12] = 0.353912;
+ rate[6][13] = 0.092310; rate[6][14] = 0.755791;
+ rate[6][15] = 0.782467; rate[6][16] = 0.914814;
+ rate[6][17] = 0.172682; rate[6][18] = 0.217549;
+ rate[6][19] = 0.655045;
+
+ rate[7][8] = 0.276379; rate[7][9] = 0.034151;
+ rate[7][10] = 0.068651; rate[7][11] = 0.415992;
+ rate[7][12] = 0.194220; rate[7][13] = 0.055288;
+ rate[7][14] = 0.273149; rate[7][15] = 1.486700;
+ rate[7][16] = 0.251477; rate[7][17] = 0.374321;
+ rate[7][18] = 0.114187; rate[7][19] = 0.209108;
+
+ rate[8][9] = 0.152215; rate[8][10] = 0.555096;
+ rate[8][11] = 0.992083; rate[8][12] = 0.450867;
+ rate[8][13] = 0.756080; rate[8][14] = 0.771387;
+ rate[8][15] = 0.822459; rate[8][16] = 0.525511;
+ rate[8][17] = 0.289998; rate[8][18] = 4.290350;
+ rate[8][19] = 0.131869;
+
+ rate[9][10] = 3.517820; rate[9][11] = 0.360574;
+ rate[9][12] = 4.714220; rate[9][13] = 1.177640;
+ rate[9][14] = 0.111502; rate[9][15] = 0.353443;
+ rate[9][16] = 1.615050; rate[9][17] = 0.234326;
+ rate[9][18] = 0.468951; rate[9][19] = 8.659740;
+
+ rate[10][11] = 0.287583; rate[10][12] = 5.375250;
+ rate[10][13] = 2.348200; rate[10][14] = 0.462018;
+ rate[10][15] = 0.382421; rate[10][16] = 0.364222;
+ rate[10][17] = 0.740259; rate[10][18] = 0.443205;
+ rate[10][19] = 1.997370;
+
+ rate[11][12] = 1.032220; rate[11][13] = 0.098843;
+ rate[11][14] = 0.619503; rate[11][15] = 1.073780;
+ rate[11][16] = 1.537920; rate[11][17] = 0.152232;
+ rate[11][18] = 0.147411; rate[11][19] = 0.342012;
+
+ rate[12][13] = 1.320870; rate[12][14] = 0.194864;
+ rate[12][15] = 0.556353; rate[12][16] = 1.681970;
+ rate[12][17] = 0.570369; rate[12][18] = 0.473810;
+ rate[12][19] = 2.282020;
+
+ rate[13][14] = 0.179896; rate[13][15] = 0.606814;
+ rate[13][16] = 0.191467; rate[13][17] = 1.699780;
+ rate[13][18] = 7.154480; rate[13][19] = 0.725096;
+
+ rate[14][15] = 1.786490; rate[14][16] = 0.885349;
+ rate[14][17] = 0.156619; rate[14][18] = 0.239607;
+ rate[14][19] = 0.351250;
+
+ rate[15][16] = 4.847130; rate[15][17] = 0.578784;
+ rate[15][18] = 0.872519; rate[15][19] = 0.258861;
+
+ rate[16][17] = 0.126678; rate[16][18] = 0.325490;
+ rate[16][19] = 1.547670;
+
+ rate[17][18] = 2.763540; rate[17][19] = 0.409817;
+
+ rate[18][19] = 0.347826;
+ }
+
+ public String getUniqueName() {
+ return "WAG";
+ }
+}
diff --git a/src/jebl/evolution/taxa/MissingTaxonException.java b/src/jebl/evolution/taxa/MissingTaxonException.java
new file mode 100644
index 0000000..920cc50
--- /dev/null
+++ b/src/jebl/evolution/taxa/MissingTaxonException.java
@@ -0,0 +1,11 @@
+package jebl.evolution.taxa;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: MissingTaxonException.java 304 2006-04-25 11:04:53Z rambaut $
+ */
+public class MissingTaxonException extends Throwable {
+ public MissingTaxonException(Taxon taxon) {
+ super("Taxon, " + taxon.getName() + ", is missing.");
+ }
+}
diff --git a/src/jebl/evolution/taxa/Taxon.java b/src/jebl/evolution/taxa/Taxon.java
new file mode 100644
index 0000000..5aeaa08
--- /dev/null
+++ b/src/jebl/evolution/taxa/Taxon.java
@@ -0,0 +1,168 @@
+/**
+ * Taxon.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.taxa;
+
+import jebl.util.Attributable;
+import jebl.util.AttributableHelper;
+
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Taxon.java 569 2006-12-12 18:48:36Z twobeers $
+ */
+public final class Taxon implements Attributable, Comparable {
+
+ /**
+ * A private constructor. Taxon objects can only be created by the static Taxon.getTaxon()
+ * factory method.
+ * @param name the name of the taxon
+ */
+ private Taxon(String name) {
+ this(name, null);
+ }
+
+ /**
+ * A private constructor. Taxon objects can only be created by the static Taxon.getTaxon()
+ * factory method.
+ * @param name the name of the taxon
+ */
+ private Taxon(String name, TaxonomicLevel taxonomicLevel) {
+ this.name = name;
+ this.taxonomicLevel = taxonomicLevel;
+ }
+
+ /**
+ * get the name of the taxon
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * get the taxonomic level of the taxon
+ * @return the taxonomic level
+ */
+ public TaxonomicLevel getTaxonomicLevel() {
+ return taxonomicLevel;
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ private AttributableHelper helper = null;
+
+ // Static factory methods
+
+ /**
+ * @return a Set containing all the currently created Taxon objects.
+ */
+ public static Set<Taxon> getAllTaxa() {
+ return Collections.unmodifiableSet(new HashSet<Taxon>(taxa.values()));
+ }
+
+ /**
+ * A static method that returns a Taxon object with the given name. If this has
+ * already been created then the same instance will be returned.
+ *
+ * Due to problems with the singleton model of taxa, this factory method now
+ * creates a new instance.
+ *
+ * @param name
+ * @return the taxon
+ */
+ public static Taxon getTaxon(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Illegal null string for taxon name");
+ }
+ if (name.length() == 0) {
+ throw new IllegalArgumentException("Illegal empty string for taxon name");
+ }
+
+ Taxon taxon = taxa.get(name);
+
+ if (taxon == null) {
+ taxon = new Taxon(name);
+ taxa.put(name, taxon);
+ }
+
+ return taxon;
+ }
+
+ // private members
+
+
+ /**
+ * The name of this taxon.
+ */
+ private final String name;
+
+ /**
+ * A hash map containing taxon name, object pairs.
+ */
+ private static Map<String, Taxon> taxa = new HashMap<String, Taxon>();
+
+ /**
+ * the taxonomic level of this taxon.
+ */
+ private final TaxonomicLevel taxonomicLevel;
+
+ public String toString() {
+ return name;
+ }
+
+ public int compareTo(Object o) {
+ return name.compareTo(((Taxon)o).getName());
+ }
+
+
+ public boolean equals(Taxon t) {
+ return name.equals(t.getName());
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/src/jebl/evolution/taxa/TaxonomicLevel.java b/src/jebl/evolution/taxa/TaxonomicLevel.java
new file mode 100644
index 0000000..8c9d30f
--- /dev/null
+++ b/src/jebl/evolution/taxa/TaxonomicLevel.java
@@ -0,0 +1,77 @@
+/*
+ * TaxonomicLevel.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.taxa;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: TaxonomicLevel.java 185 2006-01-23 23:03:18Z rambaut $
+ */
+public class TaxonomicLevel {
+
+ /**
+ * A private constructor. TaxonomicLevel objects can only be created by the static TaxonomicLevel.getTaxonomicLevel()
+ * factory method.
+ * @param name the name of the taxonomic level
+ */
+ private TaxonomicLevel(String name) {
+ this.name = name;
+ }
+
+ /**
+ * get the name of the taxonomic level
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The name of this taxonomic level
+ */
+ private final String name;
+
+ // Static factory methods
+
+ /**
+ * A static method that returns a TaxonomicLevel object with the given name. If this has
+ * already been created then the same instance will be returned.
+ * @param name the name of the taxonomic level
+ * @return the taxonomic level object
+ */
+ public static TaxonomicLevel getTaxonomicLevel(String name) {
+ TaxonomicLevel taxonomicLevel = (TaxonomicLevel)taxonomicLevels.get(name);
+
+ if (taxonomicLevel == null) {
+ taxonomicLevel = new TaxonomicLevel(name);
+ taxonomicLevels.put(name, taxonomicLevel);
+ }
+
+ return taxonomicLevel;
+ }
+
+ /**
+ * Returns a Set containing all the currently created taxonomic levels.
+ * @return
+ */
+ public static Set getTaxonomicLevels() {
+ return Collections.unmodifiableSet(taxonomicLevels.entrySet());
+ }
+
+ /**
+ * A hash map containing name, object pairs.
+ */
+ private static Map taxonomicLevels = new HashMap();
+}
diff --git a/src/jebl/evolution/treemetrics/BilleraMetric.java b/src/jebl/evolution/treemetrics/BilleraMetric.java
new file mode 100644
index 0000000..e8417d4
--- /dev/null
+++ b/src/jebl/evolution/treemetrics/BilleraMetric.java
@@ -0,0 +1,26 @@
+package jebl.evolution.treemetrics;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.TreeBiPartitionInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Billera tree distance - sum of change in branch lengths required to transform one tree to the second
+ *
+ * Note that this interface is not optimal for a large set where all pairs are required.
+ * Creating TreeBiPartitionInfo's as a pre step is better unless memory is an issue.
+ *
+ * @author Joseph Heled
+ * @version $Id$
+ */
+public class BilleraMetric implements RootedTreeMetric {
+ public double getMetric(RootedTree tree1, RootedTree tree2) {
+ List<Taxon> taxa = new ArrayList<Taxon>(tree1.getTaxa());
+ TreeBiPartitionInfo p1 = new TreeBiPartitionInfo(tree1, taxa);
+ TreeBiPartitionInfo p2 = new TreeBiPartitionInfo(tree2, taxa);
+ return TreeBiPartitionInfo.distance(p1, p2, TreeBiPartitionInfo.DistanceNorm.NORM1);
+ }
+}
diff --git a/src/jebl/evolution/treemetrics/CladeHeightMetric.java b/src/jebl/evolution/treemetrics/CladeHeightMetric.java
new file mode 100644
index 0000000..210ecd6
--- /dev/null
+++ b/src/jebl/evolution/treemetrics/CladeHeightMetric.java
@@ -0,0 +1,218 @@
+package jebl.evolution.treemetrics;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.treemetrics.RootedTreeMetric;
+
+import java.util.*;
+
+/**
+ * For each clade_j in treeB, find the MRCA_j of the taxa in clade_j in treeA.
+ *
+ * obviously, if clade_i exists in treeB then clade_i == MRCA_i.
+ *
+ * Then find the sum of squares of the differences in height:
+ *
+ * d = sqrt( sum across i[ (height(clade_i) - height(MRCA_i))^2 ] +
+ * sum across j[ (height(clade_j) - height(MRCA_j))^2 ] )
+ *
+ * The rationale is that if a clade moves then this includes the size of movement
+ * across the MRCA node and down again. I.e., a clade that moves from one side
+ * of the tree to the other scores the difference between the height of that node
+ * and the root and back down to the height of the node in the other tree.
+ *
+ * +---------A +---------A
+ * +-+ +-+
+ * | +---------B | |+--------B
+ * + ====> + ++
+ * | +---------C | +--------C
+ * +-+ |
+ * +---------D +-----------D
+ *
+ * height(tree1) tmrca(tree2) diff
+ * AB 10 10 0
+ * CD 10 12 2
+ * ABCD 12 12 0
+ *
+ * height(tree1) tmrca(tree2) diff
+ * BC 9 12 3
+ * ABC 10 10 0
+ * ABCD 12 12 0
+ *
+ * So the score is sqrt(2^2 + 3^2) = sqrt(13)
+ *
+ * Scores much less than this:
+ *
+ * +----------A +----------A
+ * + + +-+
+ * | +----------B | | +-B
+ * + ====> + +--------+
+ * | +---C | +-C
+ * +--------+ |
+ * +---D +------------D
+ *
+ * height(tree1) tmrca(tree2) diff
+ * AB 10 10 0
+ * CD 4 12 8
+ * ABCD 12 12 0
+ *
+ * height(tree1) tmrca(tree2) diff
+ * BC 2 12 10
+ * ABC 10 10 0
+ * ABCD 12 12 0
+ *
+ * So the score is sqrt(8^2 + 10^2) = sqrt(164)
+ *
+ * @author Andrew Rambaut
+ * @version $Id$
+ */
+public class CladeHeightMetric implements RootedTreeMetric {
+
+ public CladeHeightMetric() {
+ taxonMap = null;
+ }
+
+ public CladeHeightMetric(List<Taxon> taxa) {
+ taxonMap = new HashMap<Taxon, Integer>();
+ for (int i = 0; i < taxa.size(); i++) {
+ taxonMap.put(taxa.get(i), i);
+ }
+ }
+
+ public double getMetric(RootedTree tree1, RootedTree tree2) {
+
+ if (!tree1.getTaxa().equals(tree2.getTaxa())) {
+ throw new IllegalArgumentException("Trees contain different taxa");
+ }
+
+ Map<Taxon, Integer> tm = taxonMap;
+
+ if (tm == null) {
+ List<Taxon> taxa = new ArrayList<Taxon>(tree1.getTaxa());
+
+ if (!tree2.getTaxa().equals(taxa))
+ tm = new HashMap<Taxon, Integer>();
+ for (int i = 0; i < taxa.size(); i++) {
+ tm.put(taxa.get(i), i);
+ }
+ }
+
+ List<Clade> clades1 = new ArrayList<Clade>();
+ getClades(tm, tree1, tree1.getRootNode(), clades1, null);
+ Collections.sort(clades1);
+
+ List<Clade> clades2 = new ArrayList<Clade>();
+ getClades(tm, tree2, tree2.getRootNode(), clades2, null);
+ Collections.sort(clades2);
+
+ return getDistance(clades1, clades2);
+ }
+
+ private void getClades(Map<Taxon, Integer> taxonMap, RootedTree tree, Node node,
+ List<Clade> clades, BitSet bits) {
+
+ BitSet bits2 = new BitSet();
+
+ if (tree.isExternal(node)) {
+
+ int index = taxonMap.get(tree.getTaxon(node));
+ bits2.set(index);
+
+ } else {
+
+ for (Node child : tree.getChildren(node)) {
+ getClades(taxonMap, tree, child, clades, bits2);
+ }
+ clades.add(new Clade(bits2, tree.getHeight(node)));
+ }
+
+
+ if (bits != null) {
+ bits.or(bits2);
+ }
+ }
+
+ private double getDistance(List<Clade> clades1, List<Clade> clades2) {
+
+ double distance = 0.0;
+
+ for (Clade clade1 : clades1) {
+ double height1 = clade1.getHeight();
+
+ Clade clade2 = findMRCA(clade1, clades2);
+ double height2 = clade2.getHeight();
+
+ distance += (height1 - height2) * (height1 - height2);
+ }
+
+ for (Clade clade2 : clades2) {
+ double height2 = clade2.getHeight();
+
+ Clade clade1 = findMRCA(clade2, clades1);
+ double height1 = clade1.getHeight();
+
+ distance += (height1 - height2) * (height1 - height2);
+ }
+
+ return Math.sqrt(distance);
+ }
+
+ private Clade findMRCA(Clade clade1, List<Clade> clades) {
+
+ for (Clade clade2 : clades) {
+ if (isMRCA(clade1, clade2)) {
+ return clade2;
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isMRCA(Clade clade1, Clade clade2) {
+ if (clade1.getSize() > clade2.getSize()) {
+ return false;
+ }
+
+ tmpBits.clear();
+ tmpBits.or(clade1.getBits());
+ tmpBits.and(clade2.getBits());
+
+ return tmpBits.cardinality() == clade1.getSize();
+ }
+
+ BitSet tmpBits = new BitSet();
+
+ private class Clade implements Comparable<Clade> {
+ public Clade(final BitSet bits, final double height) {
+ this.bits = bits;
+ this.height = height;
+ size = bits.cardinality();
+ }
+
+ public BitSet getBits() {
+ return bits;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int compareTo(Clade clade) {
+ int i = bits.cardinality();
+ int j = clade.bits.cardinality();
+ return (i < j ? -1 : (i > j ? 1 : 0));
+ }
+
+ private final BitSet bits;
+ private final double height;
+ private final int size;
+
+ }
+
+ private final Map<Taxon, Integer> taxonMap;
+}
diff --git a/src/jebl/evolution/treemetrics/RobinsonsFouldMetric.java b/src/jebl/evolution/treemetrics/RobinsonsFouldMetric.java
new file mode 100644
index 0000000..b494b3d
--- /dev/null
+++ b/src/jebl/evolution/treemetrics/RobinsonsFouldMetric.java
@@ -0,0 +1,91 @@
+package jebl.evolution.treemetrics;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.treemetrics.RootedTreeMetric;
+
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id$
+ */
+public class RobinsonsFouldMetric implements RootedTreeMetric {
+
+ public RobinsonsFouldMetric() {
+ taxonMap = null;
+ }
+
+ public RobinsonsFouldMetric(List<Taxon> taxa) {
+ taxonMap = new HashMap<Taxon, Integer>();
+ for (int i = 0; i < taxa.size(); i++) {
+ taxonMap.put(taxa.get(i), i);
+ }
+ }
+
+ public double getMetric(RootedTree tree1, RootedTree tree2) {
+
+ Map<Taxon, Integer> tm = taxonMap;
+
+ if (tm == null) {
+ List<Taxon> taxa = new ArrayList<Taxon>(tree1.getTaxa());
+
+ tm = new HashMap<Taxon, Integer>();
+ for (int i = 0; i < taxa.size(); i++) {
+ tm.put(taxa.get(i), i);
+ }
+ }
+
+ Set<String> clades1 = getClades(tm, tree1);
+ Set<String> clades2 = getClades(tm, tree2);
+
+ clades1.removeAll(clades2);
+
+ return clades1.size();
+ }
+
+ private Set<String> getClades(Map<Taxon, Integer> taxa, RootedTree tree) {
+
+ Set<String> clades = new HashSet<String>();
+
+ getTips(taxa, tree, tree.getRootNode(), clades);
+
+ return clades;
+ }
+
+ private Set<Integer> getTips(Map<Taxon, Integer> taxa, RootedTree tree, Node node, Set<String> clades) {
+
+ Set<Integer> tips = new TreeSet<Integer>();
+
+ if (tree.isExternal(node)) {
+ tips.add(taxa.get(tree.getTaxon(node)));
+ } else {
+ Node child1 = tree.getChildren(node).get(0);
+ Set<Integer> tips1 = getTips(taxa, tree, child1, clades);
+
+ Node child2 = tree.getChildren(node).get(1);
+ Set<Integer> tips2 = getTips(taxa, tree, child2, clades);
+
+ tips.addAll(tips1);
+ tips.addAll(tips2);
+
+ clades.add(getCladeString(tips));
+ }
+
+ return tips;
+ }
+
+ private static String getCladeString(Set<Integer> tips) {
+ Iterator<Integer> iter = tips.iterator();
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(iter.next());
+ while (iter.hasNext()) {
+ buffer.append(",");
+ buffer.append(iter.next());
+ }
+ return buffer.toString();
+ }
+
+ private final Map<Taxon, Integer> taxonMap;
+}
diff --git a/src/jebl/evolution/treemetrics/RootedTreeMetric.java b/src/jebl/evolution/treemetrics/RootedTreeMetric.java
new file mode 100644
index 0000000..f86ae66
--- /dev/null
+++ b/src/jebl/evolution/treemetrics/RootedTreeMetric.java
@@ -0,0 +1,18 @@
+package jebl.evolution.treemetrics;
+
+import jebl.evolution.trees.RootedTree;
+
+/**
+ * @author rambaut
+ * Date: Jun 25, 2006
+ * Time: 12:12:34 AM
+ */
+public interface RootedTreeMetric {
+ /**
+ * calculates the metric between two rooted trees
+ * @param tree1 first tree
+ * @param tree2 second tree
+ * @return the tree metric value
+ */
+ double getMetric(RootedTree tree1, RootedTree tree2);
+}
diff --git a/src/jebl/evolution/trees/AttributedCladeSystem.java b/src/jebl/evolution/trees/AttributedCladeSystem.java
new file mode 100644
index 0000000..c4c9def
--- /dev/null
+++ b/src/jebl/evolution/trees/AttributedCladeSystem.java
@@ -0,0 +1,105 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Stores a set of unique clades for a tree
+ *
+ * @author Marc A. Suchard
+ * @version $Id: CladeSystem.java 317 2006-05-04 11:42:12 +1200 (Thu, 04 May 2006) alexeidrummond $
+ */
+public class AttributedCladeSystem extends CladeSystem {
+
+ //
+ // Public stuff
+ //
+
+ public AttributedCladeSystem(String name) {
+ attributeName = name;
+
+ }
+
+ /**
+ * @param tree
+ */
+ public AttributedCladeSystem(String name, RootedTree tree) {
+ super(tree);
+ attributeName = name;
+
+ }
+
+ private void addClades(RootedTree tree, Node node, Set<Taxon> cladeTaxa) {
+
+ if (tree.isExternal(node)) {
+ cladeTaxa.add(tree.getTaxon(node));
+ } else {
+
+ Set<Taxon> childCladeTaxa = new HashSet<Taxon>();
+ for (Node child : tree.getChildren(node)) {
+
+ addClades(tree, child, childCladeTaxa);
+ }
+
+ clades.add(new AttributedClade("tmp", childCladeTaxa));
+
+ cladeTaxa.addAll(childCladeTaxa);
+ }
+ }
+
+ private class AttributedClade {
+
+ private String attributeName;
+ private final List<Double> values = new ArrayList<Double>();
+
+ public AttributedClade(String name, Set<Taxon> taxa) {
+ this.attributeName = name;
+ this.taxa = taxa;
+ this.frequency = 1.0;
+ }
+
+ public double getFrequency() {
+ return frequency;
+ }
+
+ public void setFrequency(double frequency) {
+ this.frequency = frequency;
+ }
+
+ private double frequency;
+
+ public Set<Taxon> getTaxa() {
+ return taxa;
+ }
+
+ private final Set<Taxon> taxa;
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final AttributedClade clade = (AttributedClade) o;
+
+ if (!taxa.equals(clade.taxa)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return taxa.hashCode();
+ }
+ }
+
+
+ //
+ // Private stuff
+ //
+ private final List<AttributedClade> clades = new ArrayList<AttributedClade>();
+ private String attributeName;
+}
+
diff --git a/src/jebl/evolution/trees/BaseEdge.java b/src/jebl/evolution/trees/BaseEdge.java
new file mode 100644
index 0000000..7f0266f
--- /dev/null
+++ b/src/jebl/evolution/trees/BaseEdge.java
@@ -0,0 +1,59 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.graphs.Edge;
+import jebl.util.AttributableHelper;
+
+import java.util.Set;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Common implementation of Attributable interface used by Nodes.
+ *
+ * @author Joseph Heled
+ * @version $Id: BaseEdge.java 295 2006-04-14 14:59:10Z rambaut $
+ *
+ */
+
+abstract class BaseEdge implements Edge {
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ private AttributableHelper helper = null;
+}
diff --git a/src/jebl/evolution/trees/BaseNode.java b/src/jebl/evolution/trees/BaseNode.java
new file mode 100644
index 0000000..cdd6656
--- /dev/null
+++ b/src/jebl/evolution/trees/BaseNode.java
@@ -0,0 +1,58 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.util.AttributableHelper;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Common implementation of Attributable interface used by Nodes.
+ *
+ * @author Joseph Heled
+ * @version $Id: BaseNode.java 295 2006-04-14 14:59:10Z rambaut $
+ *
+ */
+
+abstract class BaseNode implements Node {
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ private AttributableHelper helper = null;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/CalculateSplitRates.java b/src/jebl/evolution/trees/CalculateSplitRates.java
new file mode 100644
index 0000000..d4caf91
--- /dev/null
+++ b/src/jebl/evolution/trees/CalculateSplitRates.java
@@ -0,0 +1,624 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.io.ImportException;
+import jebl.evolution.io.NexusImporter;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: msuchard
+ * Date: Dec 18, 2006
+ * Time: 1:13:51 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class CalculateSplitRates {
+
+ List<RootedTree> treeList;
+ NexusImporter importer;
+
+ public final String INDICATOR = "changed";
+ public final String RATE = "rate";
+ private List<Clade> cladeList;
+ private List<List<TimeInterval>> intervalList;
+ private DensityMap densityMap;
+
+ public CalculateSplitRates(NexusImporter importer) {
+ this.importer = importer;
+ treeList = new ArrayList<RootedTree>(100);
+ cladeList = new ArrayList<Clade>(100);
+ intervalList = new ArrayList<List<TimeInterval>>(100);
+ densityMap = new DensityMap(70, 20, 0, 0, 70, 5);
+ }
+
+// private int maxTrees = 40;
+// private int burnIn = 0;
+
+ public void loadTrees(int maxTrees, int burnIn) throws IOException, ImportException {
+ int cnt = 0;
+ int cntBurnin = 0;
+ while (importer.hasTree() && cnt < maxTrees) {
+ RootedTree tree = (RootedTree) importer.importNextTree();
+ if (cntBurnin > burnIn) {
+ treeList.add(tree);
+ cnt++;
+// addCladeRateInforamtion(tree);
+// intervalList.add( getTimeIntervals(tree) );
+// addTreeToDensityMap(tree);
+ }
+ cntBurnin++;
+ }
+ }
+
+
+ /**
+ * @param numRateBoxes Voxel count in rate dimension
+ * @param numTimeBoxes Voxel count in time dimension
+ * @return A density map of proper dimension
+ */
+ private DensityMap createDensityMap(int numRateBoxes, int numTimeBoxes) {
+ double maxTreeHeight = 0;
+ double minRate = 1;
+ double maxRate = 1;
+ for (RootedTree tree : treeList) {
+ double thisHeight = tree.getHeight(tree.getRootNode());
+ if (thisHeight > maxTreeHeight)
+ maxTreeHeight = thisHeight;
+ Set<Node> nodeList = tree.getNodes();
+ for (Node node : nodeList) {
+ if (node != tree.getRootNode()) {
+ double rate = getRate(node);
+ if (rate < minRate)
+ minRate = rate;
+ if (rate > maxRate)
+ maxRate = rate;
+ }
+ }
+ }
+ maxTreeHeight *= 1.0 + edgeFraction;
+ double rateSpread = maxRate - minRate;
+ minRate -= rateSpread * edgeFraction;
+ if (minRate < 0)
+ minRate = 0;
+ System.out.println("real max = " + maxRate);
+ maxRate += rateSpread * edgeFraction;
+ System.out.println("new max = " + maxRate);
+// System.out.println("max treeheight = "+maxTreeHeight);
+// System.out.println("min rate = "+minRate);
+// System.out.println("max rate = "+maxRate);
+// System.exit(-1);
+ DensityMap densityMap =
+ new DensityMap(numTimeBoxes, numRateBoxes, 0,
+ minRate, maxTreeHeight, maxRate);
+ for (RootedTree tree : treeList) {
+ addTreeToDensityMap(densityMap, tree);
+ }
+ return densityMap;
+ }
+
+ private void displayLongestDwellTimeInfo() {
+ for (RootedTree tree : treeList) {
+ double longestDwell = getLongestClockDwellTime(
+ getClockDwellTimes(tree));
+ double treeLenght = getTreeLength(tree);
+ System.out.printf("%5.4f\n", (longestDwell / treeLenght));
+ }
+ }
+
+ public void displayStatistics() {
+// if( treeList.size() == 0 ) {
+// System.out.println("No trees available.");
+// return;
+// }
+// System.out.println("Analyzed "+treeList.size()+" trees.");
+
+ /* if (cladeList.size() > 0) {
+ System.out.println("Clade Information:");
+ System.out.println("\t" + cladeList.size() + " observed unique clades.");
+ Collections.sort(cladeList, new CladeFrequencyComparator());
+ for (Clade clade : cladeList)
+ System.out.println("\t\t" + clade.getCount() + " : " + clade.getName());
+
+ }
+ displayIntervals();*/
+
+ // displayLongestDwellTimeInfo();
+
+// System.out.println(densityMap.toString());
+//
+
+ }
+
+
+ public void addTreeToDensityMap(DensityMap densityMap, RootedTree tree) {
+ Set<Node> nodeList = tree.getNodes();
+ for (Node node : nodeList) {
+ if (node != tree.getRootNode())
+ densityMap.addTreeBranch(tree.getHeight(node),
+ tree.getHeight(tree.getParent(node)), getRate(node));
+ }
+ }
+
+ public void displayIntervals() {
+ if (intervalList.size() == 0) {
+// System.out.println("No intervals available.");
+ return;
+ }
+ System.out.println("Interval counts:");
+ for (List<TimeInterval> timesList : intervalList) {
+ System.out.println("\t" + timesList.size() + " " + getLongestInterval(timesList));
+ }
+
+ }
+
+
+ public void findTimeIntervals() {
+
+
+ }
+
+
+ private double getRate(Node node) {
+ Double rateDouble = (Double) node.getAttribute(RATE);
+ return rateDouble.doubleValue();
+ }
+
+ private boolean rateChanged(Node node) {
+ Integer changedInt = (Integer) node.getAttribute(INDICATOR);
+ return changedInt.intValue() == 1 ? true : false;
+ }
+
+ private List<TimeInterval> getTimeIntervals(RootedTree tree) {
+ return getTimeIntervals(tree, tree.getRootNode(),
+ tree.getHeight(tree.getRootNode()), new ArrayList<TimeInterval>());
+
+ }
+
+
+ private double getLongestClockDwellTime(Map<Double, Double> dwellTimes) {
+ double time = 0;
+ Set<Double> rates = dwellTimes.keySet();
+ for (Double rate : rates) {
+ Double dwell = dwellTimes.get(rate);
+ if (dwell > time)
+ time = dwell;
+ }
+ return time;
+ }
+
+ private Map<Double, Double> getClockDwellTimes(RootedTree tree) {
+ Map<Double, Double> rateDwellTimes = new HashMap<Double, Double>();
+ Set<Node> nodes = tree.getNodes();
+ for (Node node : nodes) {
+ if (node != tree.getRootNode()) {
+ double branchLength = tree.getLength(node);
+ double rate = getRate(node);
+ Double thisRate = new Double(rate);
+ Double dwellTime = rateDwellTimes.get(thisRate);
+ if (dwellTime == null) // {
+ rateDwellTimes.put(thisRate, new Double(branchLength));
+ else
+ rateDwellTimes.put(thisRate, new Double(dwellTime.doubleValue() + branchLength));
+ }
+ }
+ // Test
+/* System.out.println("# of unique rates: "+rateDwellTimes.size());
+ Set<Double> keySet = rateDwellTimes.keySet();
+ double total = 0;
+ for (Double key : keySet) {
+ Double dwell = rateDwellTimes.get(key);
+ System.out.printf("%5.4f : %5.4f\n",key,dwell);
+ total += dwell;
+ }
+ System.out.printf("Sum of dwell = %5.4f\n",total);*/
+ return rateDwellTimes;
+
+
+ }
+
+ private double getTreeLength(RootedTree tree) {
+ double total = 0;
+ Set<Node> nodes = tree.getNodes();
+ for (Node node : nodes)
+ total += tree.getLength(node);
+ return total;
+ }
+
+ /*private List<DwellTime> getDwellTimes(RootedTree tree) {
+ return getDwellTimes(tree, tree.getRootNode(),
+ tree.getHeight(tree.getRootNode()), 0.0, new ArrayList<DwellTime>());
+ }
+
+ private List<DwellTime> getDwellTimes(RootedTree tree, Node node, double startTime, double currentLenght, List<DwellTime> dwellTimes) {
+ if (tree.isExternal(node)) {
+ DwellTime dwellTime = new DwellTime(
+ startTime, currentLenght, getRate(node)
+ );
+ dwellTimes.add(dwellTime);
+ return null;
+ }
+ List<Node> children = tree.getChildren(node);
+ for (Node child : children) {
+ double branchLength = tree.getHeight(node) - tree.getHeight(child);
+ if (rateChanged(child)) { // end dwell
+ DwellTime dwellTime = new DwellTime(startTime,currentLenght,getRate(node));
+ dwellTimes.add(dwellTime);
+
+ getDwellTimes(tree,child,tree.getHeight(node),
+ branchLength, dwellTimes);
+ } else {
+ getDwellTimes(tree,child,startTime,currentLenght+branchLength, dwellTimes);
+ }
+ }
+ return dwellTimes;
+ }*/
+
+ private List<TimeInterval> getTimeIntervals(RootedTree tree, Node node, double startTime, List<TimeInterval> intervals) {
+ if (tree.isExternal(node)) {
+ TimeInterval timeInterval = new TimeInterval(
+ startTime, tree.getHeight(node), getRate(node));
+ intervals.add(timeInterval);
+ return null;
+ }
+ List<Node> children = tree.getChildren(node);
+ for (Node child : children) {
+ if (rateChanged(child)) { // end interval
+ TimeInterval timeInterval = new TimeInterval(
+ startTime, tree.getHeight(node), getRate(node));
+ intervals.add(timeInterval);
+ getTimeIntervals(tree, child, tree.getHeight(node), intervals);
+ } else {
+ getTimeIntervals(tree, child, startTime, intervals);
+ }
+ }
+ return intervals;
+ }
+
+
+ private void addCladeRateInforamtion(RootedTree tree) {
+ for (Node node : tree.getInternalNodes()) {
+ addCladeRateInformation(tree, node);
+ }
+ for (Node node : tree.getExternalNodes()) {
+ addCladeRateInformation(tree, node);
+ }
+ }
+
+// private void addCladeRateInformationTest(RootedTree tree, Node node) {
+// Set<String> attributeNames = node.getAttributeNames();
+// for( String name : attributeNames )
+// System.out.println("n: "+name);
+// }
+
+ private double getLongestInterval(List<TimeInterval> intervals) {
+ Collections.sort(intervals);
+ return (intervals.get(intervals.size() - 1).getLength());
+ }
+
+ private void addCladeRateInformation(RootedTree tree, Node node) {
+ if (tree.getRootNode() != node) {
+ Integer changedInt = (Integer) node.getAttribute(INDICATOR);
+ Double rateDouble = (Double) node.getAttribute(RATE);
+ String name = constructUniqueName(tree, node);
+// System.out.println(name + ": "+changedInt.toString() + " " + rateDouble.toString());
+ //if( cladeList == null )
+ Clade newClade = new Clade(name);
+ int index = cladeList.indexOf(newClade);
+ if (index == -1) {
+ index = cladeList.size();
+ cladeList.add(newClade);
+ }
+ cladeList.get(index).addValues(changedInt, rateDouble);
+ }
+
+ }
+
+// private BitSet constructUniqueID(RootedTree tree, Node node) {
+// Set<Node> taxa = RootedTreeUtils.getDescendantTips(tree,node);
+// BitSet bitSet = new BitSet(taxa.size());
+// for(Node tip : taxa)
+// bitSet.set(tree.get)
+//
+//
+// }
+
+ private String constructUniqueName(RootedTree tree, Node node) {
+ if (tree.isExternal(node))
+ return tree.getTaxon(node).getName();
+ Set<Node> taxa = RootedTreeUtils.getDescendantTips(tree, node);
+ List<String> nameList = new ArrayList<String>(taxa.size());
+ for (Node tip : taxa)
+ nameList.add(tree.getTaxon(tip).getName());
+ Collections.sort(nameList);
+ StringBuffer sb = new StringBuffer();
+ int cnt = 0;
+ for (String name : nameList) {
+ if (cnt != 0)
+ sb.append(",");
+ sb.append(name);
+ cnt++;
+ }
+ return sb.toString();
+ }
+
+
+ private class DoubleStatistic {
+
+ private List<Double> data;
+ private double total;
+
+ public DoubleStatistic() {
+ data = new ArrayList<Double>(1000);
+ total = 0;
+ }
+
+ public void add(double d) {
+ data.add(d);
+ total += d;
+
+ }
+
+ public double getMean() {
+ return total / data.size();
+ }
+ }
+
+ private class DwellTime implements Comparable<DwellTime> {
+
+ private double start;
+ private double rate;
+ private double length;
+
+ public DwellTime(double start, double length, double rate) {
+ this.start = start;
+ this.length = length;
+ this.rate = rate;
+ }
+
+ public int compareTo(DwellTime dwellTime) {
+ return (int) (getLength() - dwellTime.getLength());
+ }
+
+ public double getLength() {
+ return length;
+ }
+ }
+
+ private class TimeInterval implements Comparable<TimeInterval> {
+
+ private double start;
+ private double end;
+ private double rate;
+
+ public TimeInterval(double start, double end, double rate) {
+ this.start = start;
+ this.end = end;
+ this.rate = rate;
+ }
+
+
+ public int compareTo(TimeInterval timeInterval) {
+ //return (int) (rate - timeInterval.getRate());
+ return (int) (getLength() - timeInterval.getLength());
+ }
+
+ public double getLength() {
+ return start - end;
+ }
+
+ public double getStart() {
+ return start;
+ }
+
+ public double getEnd() {
+ return end;
+ }
+
+ public double getRate() {
+ return rate;
+ }
+
+ }
+
+ private class DensityMap {
+
+ private final String SEP = "\t";
+ private final String DBL = "%5.4f";
+
+ private int binX;
+ private int binY;
+
+ private int[][] data;
+ private int[] counts;
+ private double startX;
+ private double startY;
+ private double scaleX;
+ private double scaleY;
+ private int total = 0;
+
+
+ public DensityMap(int binX, int binY,
+ double startX, double startY,
+ double endX, double endY) {
+ this.binX = binX;
+ this.binY = binY;
+ data = new int[binX][binY];
+ counts = new int[binX];
+ this.startX = startX;
+ this.startY = startY;
+ scaleX = (endX - startX) / (double) binX;
+ scaleY = (endY - startY) / (double) binY;
+ }
+
+ public void addTreeBranch(double start, double end, double y) {
+ // determine bin for y
+ int Y = (int) ((y - startY) / scaleY);
+ // determine start and end bin for x
+ int START = (int) ((start - startX) / scaleX);
+ int END = (int) ((end - startX) / scaleX);
+// System.out.println(start+":"+end+" -> "+START+":"+END);
+ for (int i = START; i <= END; i++) {
+ data[i][Y] += 1;
+ counts[i] += 1;
+ total += 1;
+ }
+ }
+
+ public String toString() {
+// double dblTotal = (double) total;
+ StringBuilder sb = new StringBuilder();
+ sb.append("0.0");
+ for (int i = 0; i < binX; i++) {
+ sb.append(SEP);
+ sb.append(String.format("%3.1f", startX + scaleX * i));
+ }
+ sb.append("\n");
+ for (int i = 0; i < binY; i++) {
+ sb.append(String.format("%3.1f", startY + scaleY * i));
+ //double dblCounts = (double)counts[i];
+ for (int j = 0; j < binX; j++) {
+ sb.append(SEP);
+ double dblCounts = (double) counts[j];
+ if (dblCounts > 0)
+ sb.append(String.format(DBL,
+ (double) data[j][i] / (double) counts[j]
+ ));
+ else
+ sb.append(String.format(DBL, 0.0));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+
+ }
+
+
+ private class Clade implements Comparable<Clade> {
+
+ private String name;
+ private List<Integer> indicatorValues;
+ private List<Double> rateValues;
+ private int count = 0;
+
+ public Clade(String name) {
+ this.name = name;
+ indicatorValues = new ArrayList<Integer>(1000);
+ rateValues = new ArrayList<Double>(1000);
+ }
+
+ public int compareTo(Clade clade) {
+ System.out.println("co");
+ return name.compareTo(clade.getName());
+ }
+
+ public void addValues(Integer inInt, Double inDouble) {
+ indicatorValues.add(inInt);
+ rateValues.add(inDouble);
+ count++;
+ }
+
+ public boolean equals(Object obj) {
+ Clade c = (Clade) obj;
+ return (name.compareTo(c.getName()) == 0);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public double getIndicatorProbability() {
+ int sum = 0;
+ for (Integer i : indicatorValues)
+ sum += i;
+ return (double) sum / (double) count;
+ }
+ }
+
+ private class CladeFrequencyComparator implements Comparator<Clade> {
+
+ public int compare(Clade cladeA, Clade cladeB) {
+ if (cladeA.getCount() > cladeB.getCount())
+ return -1;
+ if (cladeA.getCount() < cladeB.getCount())
+ return 1;
+ return cladeA.getName().compareTo(cladeB.getName());
+ }
+ }
+
+ /**
+ * @param args args[0] = Beast tree log,
+ * args[1] = max number of trees to read
+ * args[2] = number of burnin trees to discard
+ * args[3] = proportion file name,
+ * args[4] = density map filename
+ */
+
+ public static void main(String[] args) {
+ try {
+ NexusImporter importer = new NexusImporter(
+ new BufferedReader(new FileReader(new File(args[0]))));
+
+ CalculateSplitRates calculator =
+ new CalculateSplitRates(importer);
+ calculator.loadTrees(
+ Integer.parseInt(args[1]),
+ Integer.parseInt(args[2])
+ );
+ // calculator.getLongestClock();
+
+// try {
+ PrintWriter printWriter = new PrintWriter(args[3]);
+ calculator.writeLongestDwellTimeInfo(printWriter);
+ printWriter.close();
+
+// }
+
+ printWriter = new PrintWriter(args[4]);
+ calculator.writeDensityMap(printWriter);
+ printWriter.close();
+
+ //calculator.displayStatistics();
+
+ } catch (FileNotFoundException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ } catch (ImportException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ } catch (IOException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ System.exit(0);
+ }
+
+ public int numRateBoxes = 25;
+ public int numTimeBoxes = 100;
+ public double edgeFraction = 0.05;
+
+ private void writeDensityMap(PrintWriter printWriter) {
+ densityMap = createDensityMap(numRateBoxes, numTimeBoxes);
+ printWriter.println(densityMap.toString());
+
+ }
+
+ private void writeLongestDwellTimeInfo(PrintWriter printWriter) {
+ printWriter.print("DwellTime\tTreeLength\tProportion\n");
+ for (RootedTree tree : treeList) {
+ Map<Double, Double> map = getClockDwellTimes(tree);
+ double longestDwell = getLongestClockDwellTime(map);
+ //double rate = map.
+ double treeLenght = getTreeLength(tree);
+ double proportion = longestDwell / treeLenght;
+ printWriter.printf("%5.4f\t%5.4f\t%5.4f\n", longestDwell, treeLenght, proportion);
+ }
+ }
+
+}
+
+
diff --git a/src/jebl/evolution/trees/CladeSystem.java b/src/jebl/evolution/trees/CladeSystem.java
new file mode 100644
index 0000000..7617ea9
--- /dev/null
+++ b/src/jebl/evolution/trees/CladeSystem.java
@@ -0,0 +1,143 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import java.util.*;
+
+/**
+ * Stores a set of unique clades for a tree
+ *
+ * @version $Id: CladeSystem.java 317 2006-05-03 23:42:12Z alexeidrummond $
+ *
+ * @author Andrew Rambaut
+ */
+public class CladeSystem
+{
+ //
+ // Public stuff
+ //
+
+ public CladeSystem()
+ {
+ }
+
+ /**
+ * @param tree
+ */
+ public CladeSystem(RootedTree tree)
+ {
+ this.taxa = new TreeSet<Taxon>(tree.getTaxa());
+ add(tree);
+ }
+
+ /** get number of unique clades */
+ public int getCladeCount()
+ {
+ return clades.size();
+ }
+
+ public Set<Taxon> getClade(int index)
+ {
+ return clades.get(index).getTaxa();
+ }
+
+ public String getCladeString(int index)
+ {
+ StringBuffer buffer = new StringBuffer("{");
+ boolean first = true;
+ for (Taxon taxon: getClade(index)){
+ if (!first) {
+ buffer.append(", ");
+ } else {
+ first = false;
+ }
+ buffer.append(taxon.getName());
+ }
+ buffer.append("}");
+ return buffer.toString();
+ }
+
+ /** get clade frequency */
+ public double getCladeFrequency(int index)
+ {
+ return clades.get(index).getFrequency();
+ }
+
+ /** adds all the clades in the tree */
+ public void add(RootedTree tree)
+ {
+ if (taxa == null) {
+ taxa = new TreeSet<Taxon>(tree.getTaxa());
+ }
+
+ // Recurse over the tree and add all the clades (or increment their
+ // frequency if already present). The root clade is not added.
+ addClades(tree, tree.getRootNode(), null);
+ }
+
+ private void addClades(RootedTree tree, Node node, Set<Taxon> cladeTaxa) {
+
+ if (tree.isExternal(node)) {
+ cladeTaxa.add(tree.getTaxon(node));
+ } else {
+
+ Set<Taxon> childCladeTaxa= new HashSet<Taxon>();
+ for (Node child : tree.getChildren(node)) {
+
+ addClades(tree, child, childCladeTaxa);
+ }
+
+ clades.add(new Clade(childCladeTaxa));
+
+ cladeTaxa.addAll(childCladeTaxa);
+ }
+ }
+
+ private class Clade {
+ public Clade(Set<Taxon> taxa) {
+ this.taxa = taxa;
+ this.frequency = 1.0;
+ }
+
+ public double getFrequency() {
+ return frequency;
+ }
+
+ public void setFrequency(double frequency) {
+ this.frequency = frequency;
+ }
+
+ private double frequency;
+
+ public Set<Taxon> getTaxa() {
+ return taxa;
+ }
+
+ private final Set<Taxon> taxa;
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Clade clade = (Clade) o;
+
+ if (!taxa.equals(clade.taxa)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return taxa.hashCode();
+ }
+ }
+
+ //
+ // Private stuff
+ //
+ private Set<Taxon> taxa = null;
+
+ private final List<Clade> clades = new ArrayList<Clade>();
+}
+
diff --git a/src/jebl/evolution/trees/ClusteringTreeBuilder.java b/src/jebl/evolution/trees/ClusteringTreeBuilder.java
new file mode 100644
index 0000000..7f0d346
--- /dev/null
+++ b/src/jebl/evolution/trees/ClusteringTreeBuilder.java
@@ -0,0 +1,240 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.ProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An abstract base class for clustering algorithms from pairwise distances
+ *
+ * @version $Id: ClusteringTreeBuilder.java 662 2007-03-21 00:32:24Z twobeers $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Joseph Heled
+ *
+ * Adapted from Alexei Drummond BEAST code.
+ */
+
+public abstract class ClusteringTreeBuilder<T extends Tree> implements TreeBuilder<T> {
+
+ public T build() {
+ init(distanceMatrix);
+
+ // TT: This should probably be ints, and instead we should write
+ // (double) progress / totalPairs to get a non-integer division below.
+ double totalPairs = numClusters;
+ double progress = 0.0;
+
+ while (true) {
+ findNextPair(); // calculates besti, bestj
+
+ abi = alias[besti];
+ abj = alias[bestj];
+ if (numClusters < numberOfRootSubtrees)
+ break;
+
+ newCluster();
+ assert( progress <= totalPairs );
+ fireSetProgress(progress / totalPairs);
+ progress++;
+ }
+ finish();
+
+ return getTree();
+ }
+
+ public void addProgressListener(ProgressListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeProgressListener(ProgressListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void fireSetProgress(double fractionCompleted) {
+ for (ProgressListener listener : listeners) {
+ listener.setProgress(fractionCompleted);
+ }
+ }
+
+ // must be a type of list that supports the remove() operation
+ private final List<ProgressListener> listeners = new ArrayList<ProgressListener>();
+
+ /**
+ * A factory method to create a ClusteringTreeBuilder
+ * @param method build method to use.
+ * @param distances Pre computed pairwise distances.
+ * @return A tree builder using method and distance matrix
+ */
+ static public ClusteringTreeBuilder getBuilder(TreeBuilderFactory.Method method, DistanceMatrix distances) {
+ ClusteringTreeBuilder builder;
+ switch( method ) {
+ case UPGMA:
+ {
+ builder = new UPGMATreeBuilder(distances);
+ break;
+ }
+ case NEIGHBOR_JOINING:
+ default:
+ {
+ builder = new NeighborJoiningTreeBuilder(distances);
+ break;
+ }
+ }
+ return builder;
+ }
+
+ //
+ // Protected and Private stuff
+ //
+
+ /**
+ * @param distanceMatrix pair distances to use when building
+ * @param rootSubtrees Number of root subtrees. Typically 2 for rooted (completly bifurcating) or 3 for unrooted.
+ * @throws IllegalArgumentException when number of taxa is not compatibale with rootSubtrees (i.e. #taxa < #subtrees)
+ */
+ protected ClusteringTreeBuilder(DistanceMatrix distanceMatrix, int rootSubtrees) throws IllegalArgumentException {
+ this.distanceMatrix = distanceMatrix;
+
+ this.numberOfRootSubtrees = rootSubtrees;
+
+ if (distanceMatrix.getSize() < rootSubtrees) {
+ throw new IllegalArgumentException("less than " + rootSubtrees + " taxa in distance matrix");
+ }
+ }
+
+ protected abstract T getTree();
+
+ protected abstract Node createExternalNode(Taxon taxon);
+
+ protected abstract Node createInternalNode(Node[] nodes, double[] distances);
+
+ /**
+ * Inform derived class that clusters besti,bestj are being joinded into a new cluster.
+ * New cluster will be (the smaller) besti while clusters greater than bestj are shifted one space back.
+ *
+ * @return branch distances to new internal node [besti - dist , bestj dist]
+ */
+ protected abstract double[] joinClusters();
+
+ /**
+ * compute updated distance between the new cluster (besti,bestj)
+ * to any other cluster k.
+ * (i,j,k) are cluster indices in [0..numClusters-1]
+ */
+ protected abstract double updatedDistance(int k);
+
+ protected double getDist(int a, int b) {
+ return distance[alias[a]][alias[b]];
+ }
+
+ protected void init(final DistanceMatrix distanceMatrix) {
+
+ numClusters = distanceMatrix.getSize();
+ clusters = new Node[numClusters];
+
+ distance = new double[numClusters][numClusters];
+ for (int i = 0; i < numClusters; i++) {
+ for (int j = 0; j < numClusters; j++) {
+ distance[i][j] = distanceMatrix.getDistance(i, j);
+ assert (!Double.isNaN(distance[i][j]));
+ }
+ }
+
+ final List<Taxon> taxa = distanceMatrix.getTaxa();
+ for (int i = 0; i < numClusters; i++) {
+ clusters[i] = createExternalNode(taxa.get(i));
+ }
+
+ alias = new int[numClusters];
+ tipCount = new int[numClusters];
+
+ for (int i = 0; i < numClusters; i++) {
+ alias[i] = i;
+ tipCount[i] = 1;
+ }
+ }
+
+ /**
+ * Find next two clusters to join. set shared best{i,j}.
+ */
+
+ protected void findNextPair() {
+ besti = 0;
+ bestj = 1;
+ double dmin = getDist(0, 1);
+ for (int i = 0; i < numClusters-1; i++) {
+ for (int j = i+1; j < numClusters; j++) {
+ final double dist = getDist(i, j);
+ if (dist < dmin) {
+ dmin = dist;
+ besti = i;
+ bestj = j;
+ }
+ }
+ }
+ }
+
+ protected void newCluster() {
+ double[] d = joinClusters();
+ Node[] n = { clusters[abi], clusters[abj] };
+ newCluster = createInternalNode(n, d);
+
+ clusters[abi] = newCluster;
+ clusters[abj] = null;
+
+ // Update distances
+ for (int k = 0; k < numClusters; k++) {
+ if (k != besti && k != bestj) {
+ int ak = alias[k];
+ distance[ak][abi] = distance[abi][ak] = updatedDistance(k);
+ distance[ak][abj] = distance[abj][ak] = -1.0;
+ }
+ }
+ distance[abi][abi] = 0.0;
+ distance[abj][abj] = -1.0;
+
+ // Update alias
+ System.arraycopy(alias, bestj + 1, alias, bestj, numClusters - 1 - bestj);
+
+ tipCount[abi] += tipCount[abj];
+ tipCount[abj] = 0;
+
+ numClusters--;
+ }
+
+ protected void finish() {
+ distance = null;
+ }
+
+ final protected DistanceMatrix distanceMatrix;
+
+ // Number of current clusters
+ protected int numClusters;
+
+ // length numClusters
+ protected Node[] clusters;
+ protected Node newCluster;
+
+ // Indices of two clusters in [0 .. numClusters-1], besti < bestj
+ protected int besti, bestj;
+
+ // Actual index of besti,bestj into arrays (clusters,tipCount,distance)
+ private int abi, abj;
+
+ // Number of tips in cluster
+ protected int[] tipCount;
+
+ // Convert from cluster number to index in arrays
+ protected int[] alias;
+
+ // Distance between clusters
+ protected double[][] distance;
+
+ protected int numberOfRootSubtrees;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/CompactRootedTree.java b/src/jebl/evolution/trees/CompactRootedTree.java
new file mode 100644
index 0000000..8126dc9
--- /dev/null
+++ b/src/jebl/evolution/trees/CompactRootedTree.java
@@ -0,0 +1,576 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.Attributable;
+
+import java.util.*;
+
+/**
+ * A memory efficient rooted tree.
+ *
+ * - Uses a compact representation for the tree structure based primarily on indices instead of pointers
+ * and objects
+ * - Minimize penalty for unused features. Trees not using attributes or edges do not require additional
+ * per node/edge memory.
+ *
+ * Limitations:
+ * - Maximun of 2^16 nodes and 2^15 external nodes. This should not be a problem with the current
+ * sizes of phlogenetic trees we currently handle.
+ *
+ * - Some of the accessors are slower, typically the ones getting all nodes, all edges, all internal
+ * nodes etc. Traversing the tree and handling attributes speed should be fine (compared to SimpkeRootedTree)
+ *
+ * @author Joseph Heled
+ * @version $Id: CompactRootedTree.java 627 2007-01-15 03:50:40Z pepster $
+ *
+ */
+public class CompactRootedTree extends AttributableImp implements RootedTree {
+ /**
+ * Array of all nodes.
+ *
+ * Ordered by levels. i.e. for the tree (a, ((e,f), d) ) the layout is
+ *
+ * 0 1 2 3 4 5 6
+ * root a b c d e f
+ *
+ * where c = (e,f) and b = ((e,f), d)
+ *
+ * The major advantage is that all decendents of a node are grouped together.
+ * In subsequent comments nodes will be reffered to by their index in the array above.
+ */
+ SimpleRootedNode[] nodes;
+
+ /**
+ * Index of parent node of x is parent[x].
+ * For the example tree, this would be (- 0 0 2 2 3 3)
+ */
+ short[] parent;
+
+ /**
+ * Decendents of node x start at sons[x]
+ * For the example tree, this would be (1 - 3 5 - - -)
+ */
+ short[] sons;
+
+ /**
+ * Number of Decendents of node x is noSons[x]
+ * For the example tree, this would be (2 0 2 2 0 0 0)
+ *
+ * Actually the above is true only for internal nodes. External nodes contain an index into
+ * the taxa array indicating where the taxon for this node is stored. A 1 bit is added at the most
+ * significant place to separate internal from external nodes. So in fact the array would look like
+ * that (2 0x8000|0 2 2 0x8000|1 0x8000|2 0x8000|3), where taxa[0] holds a's taxon etc.
+ */
+ short[] noSons;
+
+ /** Tree has node heights information */
+ boolean hasHeights;
+
+ /** Tree has branch length information */
+ boolean hasLengths;
+
+ /**
+ * Height of node x is heights[x]
+ *
+ * Keep only heights since it is easy to compute length given easy access to
+ * parent height. (If tree has only lengths, those are stored insted).
+ */
+ double[] heights;
+
+ /**
+ * Taxon for all external nodes.
+ *
+ * Indices linking nodes to taxa are stored in noSons.
+ */
+ Taxon[] taxa;
+
+ /**
+ * Graph edges.
+ *
+ * Empty until referenced. edges are indexed according to node. edges[x] is the
+ * edge between x and it's parent (x != root)
+ */
+ SimpleRootedEdge[] edges;
+
+ private boolean conceptuallyUnrooted = false;
+
+ /**
+ * Attributes for tree, nodes and edges.
+ *
+ * attributs for node x are in all.get(x), for tree in all.get(#nodes), and for edges
+ * in all.get(#nodes + edge index) (note that edge index always > 0)
+ */
+ Map<Short, Map<String, Object> > all = null;
+
+ /**
+ * Test if attribute map exists for index
+ * @param index
+ * @return true if map exists
+ */
+ private boolean hasAttributeMap(short index) {
+ return all != null && all.get(index) != null;
+ }
+
+ /**
+ * Attribute map for index (node, tree or edge).
+ *
+ * @param index
+ * @return attribute map
+ */
+ private Map<String, Object> aMap(short index) {
+ if( all == null ) {
+ all = new HashMap<Short, Map<String, Object>>();
+ }
+
+ Map<String, Object> map = all.get(index);
+ if( map == null ) {
+ map = new HashMap<String, Object>();
+ all.put(index, map);
+ }
+ return map;
+ }
+
+ /**
+ * A minimal rooted node.
+ */
+ private class SimpleRootedNode extends AttributableImp implements Node {
+ // Index of node in tree nodes array.
+ private short index;
+
+ SimpleRootedNode(short index) {
+ this.index = index;
+ }
+
+ public int getDegree() {
+ return nSons(index) + 1;
+ }
+
+ Map<String, Object> getExistingMap() {
+ if( hasAttributeMap(index) ) {
+ return aMap(index);
+ }
+ return null;
+ }
+
+ Map<String, Object> getMap() {
+ return aMap(index);
+ }
+ }
+
+ // Number of decendents.
+ private int nSons(int index) {
+ // Take care of external node bit.
+ final short n = noSons[index];
+ if( (n & 0x8000) == 0) {
+ return n;
+ }
+ return 0;
+ }
+
+ /** Minimal edge object */
+ private class SimpleRootedEdge extends AttributableImp implements Edge {
+ /** edge is between node 'index' and it's parent
+ *
+ * As a consequence, index > 0 always.
+ */
+ private short index;
+
+ SimpleRootedEdge(short index) {
+ this.index = index;
+ }
+
+ Map<String, Object> getExistingMap() {
+ final short i = (short)(nodes.length + index);
+ if( hasAttributeMap(i) ) {
+ return aMap(i);
+ }
+ return null;
+ }
+
+ Map<String, Object> getMap() {
+ return aMap((short)(nodes.length + index));
+ }
+
+ public double getLength() {
+ return heights[parent[index]] - heights[index];
+ }
+ }
+
+ /**
+ * Do all the hard work.
+ *
+ * @param t
+ */
+ public CompactRootedTree(RootedTree t) {
+ conceptuallyUnrooted = t.conceptuallyUnrooted();
+
+ final int nNodes = t.getNodes().size();
+ nodes = new SimpleRootedNode[nNodes];
+ parent = new short[nNodes];
+ sons = new short[nNodes];
+ noSons = new short[nNodes];
+ heights = new double[nNodes];
+ hasHeights = t.hasHeights();
+ hasLengths = t.hasLengths();
+ taxa = new Taxon[t.getTaxa().size()];
+ edges = null;
+
+ final Node rootNode = t.getRootNode();
+ // nodes to be inserted to the tree, all the same distance from root.
+ List<Node> level = new ArrayList<Node>();
+
+ // nodes to be inserted in next iteration (decendents of nodes in 'level')
+ List<Node> nlevel = new ArrayList<Node>();
+
+ // start with roo node
+ level.add(rootNode);
+ // where next inserted node goes
+ int iNode = 0;
+ // where decendents (if any) of next node goes
+ int decendentslStart = 1;
+ // where taxa (if external) of next node goes
+ int nTax = 0;
+
+ while( level.size() > 0 ) {
+ nlevel.clear();
+ for( Node n : level ) {
+ short ns = (short) (n.getDegree() - 1);
+
+ if( hasHeights ) {
+ heights[iNode] = t.getHeight(n);
+ } else if( hasLengths ) {
+ heights[iNode] = ((iNode == 0) ? 0.0 : t.getLength(n));
+ }
+
+ nodes[iNode] = new SimpleRootedNode((short)iNode);
+ sons[iNode] = ns > 0 ? (short)decendentslStart : 0;
+ for(int l = 0; l < ns; ++l) {
+ parent[decendentslStart + l] = (short)iNode;
+ }
+
+ decendentslStart += ns;
+
+ if( ns == 0 ) {
+ // external, set taxon and mark it.
+ assert t.isExternal(n);
+ taxa[nTax] = t.getTaxon(n);
+ ns = (short)(0x8000 | nTax);
+ ++nTax;
+ }
+ noSons[iNode] = ns;
+
+ // set node attributes
+ final Map<String, Object> map = n.getAttributeMap();
+ if( map.size() > 0 ) {
+ nodes[iNode].getMap().putAll(map);
+ }
+
+ // add decendents for next round
+ for( Node s : t.getChildren(n) ) {
+ nlevel.add(s);
+ }
+
+ ++iNode;
+ }
+ // setup for next level
+ level.clear();
+ level.addAll(nlevel);
+ }
+
+ // add tree attributes
+ final Map<String, Object> map = t.getAttributeMap();
+ if( map.size() > 0 ) {
+ getMap().putAll(map);
+ }
+ }
+
+ public List<Node> getChildren(Node node) {
+ final int index = ((SimpleRootedNode) node).index;
+ final int nSon = nSons(index);
+ final ArrayList<Node> clist = new ArrayList<Node>(nSon);
+ for(int k = sons[index]; k < sons[index] + nSon; ++k) {
+ clist.add(nodes[k]);
+ }
+ return clist;
+ }
+
+ public boolean hasHeights() {
+ return hasHeights;
+ }
+
+ public double getHeight(Node node) {
+ assert hasHeights;
+
+ return heights[((SimpleRootedNode)node).index];
+ }
+
+ public boolean hasLengths() {
+ return hasLengths;
+ }
+
+ public double getLength(Node node) {
+ assert hasLengths;
+
+ final int index = ((SimpleRootedNode) node).index;
+ if( hasHeights ) {
+ if( index == 0 ) return 0;
+ return heights[parent[index]] - heights[index];
+ }
+ return heights[index];
+ }
+
+ public Node getParent(Node node) {
+ final int index = ((SimpleRootedNode) node).index;
+ return index == 0 ? null : nodes[parent[index]];
+ }
+
+ public Node getRootNode() {
+ return nodes[0];
+ }
+
+ public boolean conceptuallyUnrooted() {
+ return conceptuallyUnrooted;
+ }
+
+ public void setConceptuallyUnrooted(boolean conceptuallyUnrooted) {
+ this.conceptuallyUnrooted = conceptuallyUnrooted;
+ }
+
+ public boolean isRoot(Node node) {
+ return ((SimpleRootedNode)node).index == 0;
+ }
+
+ // O(number of nodes)
+ public Set<Node> getExternalNodes() {
+ Set<Node> n = new HashSet<Node>();
+ for(int i = 0; i < nodes.length; ++i) {
+ if( (noSons[i] & 0x8000) != 0 ) {
+ n.add(nodes[i]);
+ }
+ }
+ return n;
+ }
+
+ // O(number of nodes)
+ public Set<Node> getInternalNodes() {
+ Set<Node> n = new HashSet<Node>();
+ for(int i = 0; i < nodes.length; ++i) {
+ if( (noSons[i] & 0x8000) == 0 ) {
+ n.add(nodes[i]);
+ }
+ }
+ return n;
+ }
+
+ public Set<Edge> getExternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getExternalNodes()) {
+ edges.add( establishEdge( ((SimpleRootedNode)node).index) ) ;
+ }
+ return edges;
+ }
+
+ public Set<Edge> getInternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getInternalNodes()) {
+ if (node != getRootNode()) {
+ edges.add( establishEdge( ((SimpleRootedNode)node).index) );
+ }
+ }
+ return edges;
+ }
+
+ public Set<Taxon> getTaxa() {
+ return new HashSet<Taxon>(Arrays.asList(taxa));
+ }
+
+ public Taxon getTaxon(Node node) {
+ final short index = ((SimpleRootedNode) node).index;
+ if( (noSons[index] & 0x8000) != 0 ) {
+ return taxa[noSons[index] & 0x7FFF];
+ }
+ return null;
+ }
+
+ public boolean isExternal(Node node) {
+ return nSons(((SimpleRootedNode)node).index) == 0;
+ }
+
+ // O(number of nodes)
+ public Node getNode(Taxon taxon) {
+ int i = Arrays.asList(taxa).indexOf(taxon);
+ for(int k = 0; k < nodes.length; ++k) {
+ if( noSons[k] == (short)(0x8000 | i) ) {
+ return nodes[k];
+ }
+ }
+ return null;
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ for(int n = 0; n < taxa.length; ++n) {
+ if( from.equals(taxa[n]) ) {
+ taxa[n] = to;
+ break;
+ }
+ }
+ }
+
+ public List<Edge> getEdges(Node node) {
+ List<Edge> e = new ArrayList<Edge>();
+ final short index = ((SimpleRootedNode) node).index;
+ if( index != 0 ) {
+ e.add(establishEdge(index));
+ }
+ for(int n = 0; n < nSons(index); ++n) {
+ final short sindex = (short) (sons[index] + n);
+ e.add(establishEdge(sindex));
+ }
+ return e;
+ }
+
+ public List<Node> getAdjacencies(Node node) {
+ List<Node> adjacencies = new ArrayList<Node>();
+ final short index = ((SimpleRootedNode) node).index;
+ final int nSon = nSons(index);
+ final short sonStart = sons[index];
+ for(int n = 0; n < nSon; ++n) {
+ adjacencies.add( nodes[sonStart + n] );
+ }
+ if( index != 0 ) {
+ adjacencies.add( nodes[parent[index]] );
+ }
+ return adjacencies;
+ }
+
+ private Edge establishEdge(short index) {
+ if( edges == null ) {
+ edges = new SimpleRootedEdge[nodes.length];
+ }
+ if( edges[index] == null ) {
+ edges[index] = new SimpleRootedEdge(index);
+ }
+ return edges[index];
+ }
+
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ short index1 = ((SimpleRootedNode) node1).index;
+ short index2 = ((SimpleRootedNode) node2).index;
+ // make index1 the parent of index2
+ if( parent[index1] == index2 ) {
+ index2 = index1;
+ } else if( parent[index2] != index1 ) {
+ throw new NoEdgeException();
+ }
+ // from this point on index1 is invalid
+
+ return establishEdge(index2);
+ }
+
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ final short index1 = ((SimpleRootedNode) node1).index;
+ final short index2 = ((SimpleRootedNode) node2).index;
+ if( ! (parent[index1] == index2 || parent[index2] == index1) ) {
+ throw new NoEdgeException();
+ }
+ return Math.abs(heights[index1] - heights[index2]);
+ }
+
+ public Node[] getNodes(Edge edge) {
+ Node[] ns = new Node[2];
+ final short index = ((SimpleRootedEdge) edge).index;
+ ns[0] = nodes[index];
+ ns[1] = nodes[parent[index]];
+
+ return ns;
+ }
+
+ public Set<Node> getNodes() {
+ return new HashSet<Node>(Arrays.asList(nodes));
+ }
+
+ public Set<Edge> getEdges() {
+ for(int k = 1; k < nodes.length; ++k) {
+ establishEdge((short)k);
+ }
+ return new HashSet<Edge>( Arrays.asList(edges));
+ }
+
+ public Set<Node> getNodes(int degree) {
+ Set<Node> ns = new HashSet<Node>();
+ // check non root nodes
+ for(int k = 1; k < nodes.length; ++k) {
+ if( degree == nSons(k) + 1 )
+ ns.add(nodes[k]);
+ }
+ // check root
+ if( nSons(0) == degree ) {
+ ns.add(nodes[0]);
+ }
+ return ns;
+ }
+
+ Map<String, Object> getExistingMap() {
+ final short index = (short) nodes.length;
+ if( hasAttributeMap(index) ) {
+ return aMap(index);
+ }
+ return null;
+ }
+
+ Map<String, Object> getMap() {
+ final short index = (short) nodes.length;
+ return aMap(index);
+ }
+}
+
+/**
+ * Helper in attribute handling for tree, nodes and edges.
+ *
+ * The object provides the map via the abstract methods.
+ */
+abstract class AttributableImp implements Attributable {
+ /**
+ * Get attribute map for object.
+ * @return the map
+ */
+ abstract Map<String, Object> getMap();
+
+ /**
+ * Used to avoid creating an attribute object when only querying for map elements.
+ *
+ * @return Attribute map for object if a none-empty one exists for object, null otherwise
+ */
+ abstract Map<String, Object> getExistingMap();
+
+
+ public void setAttribute(String name, Object value) {
+ getMap().put(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ return getMap().get(name);
+ }
+
+ public void removeAttribute(String name) {
+ getMap().remove(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ Map<String, Object> map = getExistingMap();
+ if( map != null ) {
+ return map.keySet();
+ }
+ return Collections.emptySet();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ Map<String, Object> map = getExistingMap();
+ if( map != null ) {
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/ConsensusTreeBuilder.java b/src/jebl/evolution/trees/ConsensusTreeBuilder.java
new file mode 100644
index 0000000..2a2dd4d
--- /dev/null
+++ b/src/jebl/evolution/trees/ConsensusTreeBuilder.java
@@ -0,0 +1,128 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.util.ProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ *
+ * Build a consensus tree for a set of trees. Base class just check for consistency
+ * Work in progress.
+ *
+ * @author Joseph Heled
+ * @version $Id: ConsensusTreeBuilder.java 616 2007-01-09 21:55:13Z pepster $
+ */
+
+public abstract class ConsensusTreeBuilder<T extends Tree> implements TreeBuilder<T> {
+
+ /** Name of attribute specifing amount of support for branch */
+ final static public String DEFAULT_SUPPORT_ATTRIBUTE_NAME = "Consensus support(%)";
+
+ private String supportAttributeName;
+ private boolean supportAsPercent;
+
+ /**
+ * Supported consesus methods.
+ */
+ public enum Method { GREEDY("Greedy"), MRCAC("MRCA Clustering");
+
+ Method(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private String name;
+ }
+
+ /** Number of external nodes/taxa */
+ protected final int nExternalNodes;
+
+ /** List of common taxa in all trees */
+ protected List<Taxon> taxons;
+
+
+ /**
+ * Check for consistancy and establish the common taxa
+ * @param trees
+ */
+ ConsensusTreeBuilder(Tree[] trees) {
+ this(trees, DEFAULT_SUPPORT_ATTRIBUTE_NAME, true);
+ }
+
+ /**
+ * Check for consistmcy and establish the common taxa
+ * @param trees to build summary tree from
+ * @param supportAttributeName name of attribute describing amount of support
+ * @param asPercent when true, support is in percent (0 - 100), otherwise in number of trees
+ * from the set.
+ */
+ ConsensusTreeBuilder(Tree[] trees, String supportAttributeName, boolean asPercent) {
+ Tree first = trees[0];
+ this.supportAttributeName = supportAttributeName;
+ this.supportAsPercent = asPercent;
+
+ nExternalNodes = first.getExternalNodes().size();
+
+ final Set<Taxon> taxa = first.getTaxa();
+ taxons = new ArrayList<Taxon>(taxa);
+
+ for (Tree t : trees) {
+ final int nExternal = t.getExternalNodes().size();
+ if (nExternal != nExternalNodes || !t.getTaxa().containsAll(taxa)) {
+ throw new IllegalArgumentException("Non compatible trees");
+ }
+ }
+ }
+
+ abstract public String getMethodDescription();
+
+ protected String getSupportDescription(double supportThreshold) {
+ String supporDescription;
+ if( supportThreshold == 1.0 ) {
+ supporDescription = "Strict";
+ } else if( supportThreshold == .5 ) {
+ supporDescription = "Majority";
+ } else {
+ supporDescription = "Above " + (100*supportThreshold) + "% support";
+ }
+ return supporDescription;
+ }
+
+ public String getSupportAttributeName() {
+ return supportAttributeName;
+ }
+
+ public boolean isSupportAsPercent() {
+ return supportAsPercent;
+ }
+
+ public void addProgressListener(ProgressListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeProgressListener(ProgressListener listener) {
+ listeners.remove(listener);
+ }
+
+ protected boolean fireSetProgress(double fractionCompleted) {
+ boolean requestStop = false;
+ for (ProgressListener listener : listeners) {
+ if (listener.setProgress(fractionCompleted)) {
+ requestStop = true;
+ }
+ }
+ return requestStop;
+ }
+
+ private final List<ProgressListener> listeners = new ArrayList<ProgressListener>();
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/FilteredRootedTree.java b/src/jebl/evolution/trees/FilteredRootedTree.java
new file mode 100644
index 0000000..b455a54
--- /dev/null
+++ b/src/jebl/evolution/trees/FilteredRootedTree.java
@@ -0,0 +1,155 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: FilteredRootedTree.java 627 2007-01-15 03:50:40Z pepster $
+ */
+public abstract class FilteredRootedTree implements RootedTree {
+
+ public FilteredRootedTree(final RootedTree source) {
+ this.source = source;
+ }
+
+ public RootedTree getSource() {
+ return source;
+ }
+
+ public boolean conceptuallyUnrooted() {
+ return source.conceptuallyUnrooted();
+ }
+
+ public List<Node> getChildren(Node node) {
+ return source.getChildren(node);
+ }
+
+ public boolean hasHeights() {
+ return source.hasHeights();
+ }
+
+ public double getHeight(Node node) {
+ return source.getHeight(node);
+ }
+
+ public boolean hasLengths() {
+ return source.hasLengths();
+ }
+
+ public double getLength(Node node) {
+ return source.getLength(node);
+ }
+
+ public Node getParent(Node node) {
+ return source.getParent(node);
+ }
+
+ public Node getRootNode() {
+ return source.getRootNode();
+ }
+
+ public Set<Node> getExternalNodes() {
+ return source.getExternalNodes();
+ }
+
+ public Set<Node> getInternalNodes() {
+ return source.getInternalNodes();
+ }
+
+ public Set<Edge> getExternalEdges() {
+ return source.getExternalEdges();
+ }
+
+ public Set<Edge> getInternalEdges() {
+ return source.getInternalEdges();
+ }
+
+ public Node getNode(Taxon taxon) {
+ return source.getNode(taxon);
+ }
+
+ public Set<Taxon> getTaxa() {
+ return source.getTaxa();
+ }
+
+ public Taxon getTaxon(Node node) {
+ return source.getTaxon(node);
+ }
+
+ public boolean isExternal(Node node) {
+ return source.isExternal(node);
+ }
+
+ public List<Node> getAdjacencies(Node node) {
+ return source.getAdjacencies(node);
+ }
+
+ public List<Edge> getEdges(Node node) {
+ return source.getEdges(node);
+ }
+
+ public Set<Edge> getEdges() {
+ return source.getEdges();
+ }
+
+ public Node[] getNodes(Edge edge) {
+ return source.getNodes(edge);
+ }
+
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ return source.getEdge(node1, node2);
+ }
+
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ return source.getEdgeLength(node1, node2);
+ }
+
+ public Set<Node> getNodes() {
+ return source.getNodes();
+ }
+
+ public Set<Node> getNodes(int degree) {
+ return source.getNodes(degree);
+ }
+
+ public boolean isRoot(Node node) {
+ return source.isRoot(node);
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ source.renameTaxa(from, to);
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ source.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ return source.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ source.removeAttribute(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ return source.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ return source.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ protected final RootedTree source;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/GreedyRootedConsensusTreeBuilder.java b/src/jebl/evolution/trees/GreedyRootedConsensusTreeBuilder.java
new file mode 100644
index 0000000..472cdd3
--- /dev/null
+++ b/src/jebl/evolution/trees/GreedyRootedConsensusTreeBuilder.java
@@ -0,0 +1,298 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.util.FixedBitSet;
+
+import java.util.*;
+
+/**
+ * Date: 5/03/2006
+ * Time: 09:40:18
+ *
+ * @author Joseph Heled
+ * @version $Id: GreedyRootedConsensusTreeBuilder.java 616 2007-01-09 21:55:13Z pepster $
+ * <p/>
+ * Implementation shares some code with GreedyUnrootedConsensusTreeBuilder (which preceded it), and perhaps I will
+ * find a way to merge the two at a later stage when I have the time.
+ */
+public class GreedyRootedConsensusTreeBuilder extends ConsensusTreeBuilder<RootedTree> {
+ /**
+ * Set of trees.
+ */
+ private final RootedTree[] rtrees;
+
+
+ /**
+ * Consensus contains only clades having at least that amount of support in set. Traditionally 50%
+ */
+ private final double supportThreshold;
+
+ public GreedyRootedConsensusTreeBuilder(RootedTree[] trees, double supportThreshold) {
+ super(trees);
+ this.rtrees = trees;
+ this.supportThreshold = supportThreshold;
+ }
+
+ public GreedyRootedConsensusTreeBuilder(RootedTree[] trees, double supportThreshold, String supportAttributeName, boolean asPercent) {
+ super(trees, supportAttributeName, asPercent);
+ this.rtrees = trees;
+ this.supportThreshold = supportThreshold;
+ }
+
+ public String getMethodDescription() {
+ String supporDescription = getSupportDescription(supportThreshold);
+ return supporDescription + " greedy clustering";
+ }
+
+ /**
+ * One clade support.
+ */
+ static final class Support {
+ /**
+ * number of trees containing the clade.
+ */
+ private int nTreesWithClade;
+ /**
+ * Sum of node heights of trees containing the clade.
+ */
+ private double sumBranches;
+
+ Support() {
+ sumBranches = 0.0;
+ nTreesWithClade = 0;
+ }
+
+ public final void add(double height) {
+ sumBranches += height;
+ ++nTreesWithClade;
+ }
+ }
+
+ private final boolean debug = false;
+
+ private String tipsAsText(FixedBitSet b) {
+ String names = "(";
+ for (int i = b.nextOnBit(0); i >= 0; i = b.nextOnBit(i + 1)) {
+ names = names + taxons.get(i).getName() + ",";
+ }
+ return names + ")";
+ }
+
+ private FixedBitSet rootedSupport(RootedTree tree, Node node, Map<FixedBitSet, Support> support) {
+ FixedBitSet clade = new FixedBitSet(nExternalNodes);
+ if (tree.isExternal(node)) {
+ clade.set(taxons.indexOf(tree.getTaxon(node)));
+ } else {
+ for (Node n : tree.getChildren(node)) {
+ FixedBitSet childClade = rootedSupport(tree, n, support);
+ clade.union(childClade);
+ }
+ }
+
+ Support s = support.get(clade);
+ if (s == null) {
+ s = new Support();
+ support.put(clade, s);
+ }
+ s.add(Utils.safeNodeHeight(tree, node));
+ return clade;
+ }
+
+ /**
+ * Make sure subtree below node has consistent heights, i.e. node height is higher than it's descendants
+ *
+ * @param tree
+ * @param node
+ * @return height of node
+ */
+ private double insureConsistency(MutableRootedTree tree, Node node) {
+ double height = Utils.safeNodeHeight(tree, node);
+ if (tree.isExternal(node)) {
+ return height;
+ } else {
+ for (Node n : tree.getChildren(node)) {
+ final double childHeight = insureConsistency(tree, n);
+ height = Math.max(height, childHeight);
+ }
+ }
+
+ tree.setHeight(node, height);
+ return height;
+ }
+
+
+ public final RootedTree build() {
+
+ // establish support
+ Map<FixedBitSet, Support> support = new HashMap<FixedBitSet, Support>();
+ int k = 0;
+ for (RootedTree tree : rtrees) {
+ if (debug) {
+ System.out.println("Tree: " + Utils.DEBUGsubTreeRep(tree, tree.getRootNode()));
+ }
+ rootedSupport(tree, tree.getRootNode(), support);
+
+ ++k;
+ if (fireSetProgress( (0.9 * k) / rtrees.length)) {
+ return null;
+ }
+ }
+
+ final int nTrees = rtrees.length;
+
+ MutableRootedTree consTree = new MutableRootedTree();
+
+ // Contains all internal nodes in the tree so far, ordered so descendants
+ // appear later than ancestors
+ List<Node> internalNodes = new ArrayList<Node>(nExternalNodes);
+
+ // For each internal node, a bit-set with the complete set of tips for it's clade
+ List<FixedBitSet> internalNodesTips = new ArrayList<FixedBitSet>(nExternalNodes);
+ assert taxons.size() == nExternalNodes;
+
+ // establish a tree with one root having all tips as descendants
+ internalNodesTips.add(new FixedBitSet(nExternalNodes));
+ FixedBitSet rooNode = internalNodesTips.get(0);
+ Node[] nodes = new Node[nExternalNodes];
+ for (int nt = 0; nt < taxons.size(); ++nt) {
+ nodes[nt] = consTree.createExternalNode(taxons.get(nt));
+ rooNode.set(nt);
+ }
+
+ internalNodes.add(consTree.createInternalNode(Arrays.asList(nodes)));
+
+ // sorts support from largest to smallest
+ final Comparator<Map.Entry<FixedBitSet, Support>> comparator = new Comparator<Map.Entry<FixedBitSet, Support>>() {
+ public int compare(Map.Entry<FixedBitSet, Support> o1, Map.Entry<FixedBitSet, Support> o2) {
+ return o2.getValue().nTreesWithClade - o1.getValue().nTreesWithClade;
+ }
+ };
+
+ // add everything to queue
+ PriorityQueue<Map.Entry<FixedBitSet, Support>> queue =
+ new PriorityQueue<Map.Entry<FixedBitSet, Support>>(support.size(), comparator);
+
+ for (Map.Entry<FixedBitSet, Support> se : support.entrySet()) {
+ Support s = se.getValue();
+ FixedBitSet clade = se.getKey();
+ final int cladeSize = clade.cardinality();
+ if (cladeSize == nExternalNodes) {
+ // root
+ consTree.setHeight(consTree.getRootNode(), s.sumBranches / nTrees);
+ continue;
+ }
+
+ if (s.nTreesWithClade == nTrees && cladeSize == 1) {
+ // leaf/external node
+ final int nt = clade.nextOnBit(0);
+ final Node leaf = consTree.getNode(taxons.get(nt));
+ consTree.setHeight(leaf, s.sumBranches / nTrees);
+ } else {
+ queue.add(se);
+ }
+
+ if (fireSetProgress(0.95)) {
+ return null;
+ }
+ }
+
+ while (queue.peek() != null) {
+ Map.Entry<FixedBitSet, Support> e = queue.poll();
+ final Support s = e.getValue();
+
+ final double psupport = (1.0 * s.nTreesWithClade) / nTrees;
+ if (psupport < supportThreshold) {
+ break;
+ }
+
+ final FixedBitSet cladeTips = e.getKey();
+
+ if (debug) {
+ System.out.println(100.0 * psupport + " Split: " + cladeTips + " " + tipsAsText(cladeTips));
+ }
+
+ boolean found = false;
+
+ // locate the node containing the clade. going in reverse order insures the lowest one is hit first
+ for (int nsub = internalNodesTips.size() - 1; nsub >= 0; --nsub) {
+
+ FixedBitSet allNodeTips = internalNodesTips.get(nsub);
+
+ // size of intersection between tips & split
+ final int nSplit = allNodeTips.intersectCardinality(cladeTips);
+
+ if (nSplit == cladeTips.cardinality()) {
+ // node contains all of clade
+
+ // Locate node descendants containing the split
+ found = true;
+ List<Integer> split = new ArrayList<Integer>();
+
+ Node n = internalNodes.get(nsub);
+ int l = 0;
+ List<Node> children = consTree.getChildren(n);
+ for (Node ch : children) {
+ if (consTree.isExternal(ch)) {
+ if (cladeTips.contains(taxons.indexOf(consTree.getTaxon(ch)))) {
+ split.add(l);
+ }
+ } else {
+ // internal
+ final int o = internalNodes.indexOf(ch);
+ final int i = internalNodesTips.get(o).intersectCardinality(cladeTips);
+ if (i == internalNodesTips.get(o).cardinality()) {
+ split.add(l);
+ } else if (i > 0) {
+ // Non compatible
+ found = false;
+ break;
+ }
+ }
+ ++l;
+ }
+
+
+ if (! (found && split.size() < children.size())) {
+ found = false;
+ break;
+ }
+
+ if (split.size() == 0) {
+ System.out.println("Bug??");
+ assert(false);
+ }
+
+ final Node detached = consTree.detachChildren(n, split);
+ final double height = s.sumBranches / s.nTreesWithClade;
+ consTree.setHeight(detached, height);
+
+ detached.setAttribute(getSupportAttributeName(), isSupportAsPercent() ? 100 * psupport : psupport);
+
+ if (debug) {
+ System.out.println("detached:" + Utils.DEBUGsubTreeRep(consTree, detached) + " len " + height + " sup " + psupport);
+ System.out.println("tree: " + Utils.toNewick(consTree));
+ }
+
+ // insert just after parent, so before any descendants
+ internalNodes.add(nsub + 1, detached);
+ internalNodesTips.add(nsub + 1, new FixedBitSet(cladeTips));
+
+ break;
+ }
+ }
+
+ if (psupport >= .5 && ! found) {
+ System.out.println("Bug??");
+ assert(false);
+ }
+
+ if (fireSetProgress(0.99)) {
+ return null;
+ }
+ }
+
+ insureConsistency(consTree, consTree.getRootNode());
+ fireSetProgress(1.0);
+ return consTree;
+ }
+}
diff --git a/src/jebl/evolution/trees/GreedyUnrootedConsensusTreeBuilder.java b/src/jebl/evolution/trees/GreedyUnrootedConsensusTreeBuilder.java
new file mode 100644
index 0000000..a404b65
--- /dev/null
+++ b/src/jebl/evolution/trees/GreedyUnrootedConsensusTreeBuilder.java
@@ -0,0 +1,416 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.FixedBitSet;
+
+import java.util.*;
+
+/**
+ * Builds greedy consensus tree given a set of unrooted trees.
+ * <p/>
+ * Each edge in a tree "supports" a split, i.e. the partition of the taxa into two clades
+ * (which you get by deleting the edge). A Majority consensus tree is built by finding
+ * clades appearing in at least 50% of the trees or more. A Greedy consensus tree is a
+ * refinement of a Majority tree where the splits are sorted by amount of support, and are
+ * applied in order only if they are compatible (consistent) with the tree at that
+ * stage. A user supplied threshold gives a lower bound on amount of support. If set t0
+ * 50% the Greedy method reduces to the majority consensus tree. At 100% it reduces to the
+ * Strict consensus tree.
+ * <p/>
+ * The implementation is relatively simple but tricky in parts. Each tree is scanned, and
+ * support for each split/clade is collected in one table. The clade is represented by a
+ * bitset, which always contains the (arbitrary) first node. The scan is made by going
+ * over all nodes ordered in such a way that the subtree of exactly one edge of the node
+ * has been completely scanned, so the node "knows" the set of tips of that subtree without
+ * needing to re-scan the tree.
+ * <p/>
+ * After all trees are scanned an initial consensus tree is constructed with one root and
+ * all tips as children. The split set is scanned in order of decreasing support, and each
+ * supported clade refines the tree by creating a new descendant for the node containing
+ * the clade and re-attaching the clade to that new node. This is done only if the split
+ * is compatible with the tree, i.e. only if the split is completely contained in a proper
+ * subset of descendants of one node. This process continues until only clades with support
+ * lower that the threshold are left.
+ * <p/>
+ * The length of the consensus tree branches is computed from the average over all trees
+ * containing the clade. The lengths of tip branches are computed by averaging over all
+ * trees.
+ * <p/>
+ * While the consensus tree is logically unrooted, we generate a rooted tree because we
+ * can store attributes such as support only for nodes.
+ *
+ * @author Joseph Heled
+ * @version $Id: GreedyUnrootedConsensusTreeBuilder.java 616 2007-01-09 21:55:13Z pepster $
+ */
+
+public final class GreedyUnrootedConsensusTreeBuilder extends ConsensusTreeBuilder<Tree> {
+ /**
+ * Set of trees.
+ */
+ private final Tree[] trees;
+
+ /**
+ * Outgroup, if any. Currently used only for display purposes.
+ */
+ private final Taxon outGroup;
+
+ /**
+ * Consensus contains only clades having at least that amount of support in set. Traditionally 50%
+ */
+ private final double supportThreshold;
+
+ public GreedyUnrootedConsensusTreeBuilder(Tree[] trees, Taxon outGroup, double supportThreshold) {
+ super(trees);
+ this.trees = trees;
+ this.outGroup = outGroup;
+ this.supportThreshold = supportThreshold;
+ }
+
+ GreedyUnrootedConsensusTreeBuilder(Tree[] trees, Taxon outGroup, double supportThreshold, String supportAttributeName, boolean asPercent) {
+ super(trees, supportAttributeName, asPercent);
+ this.trees = trees;
+ this.outGroup = outGroup;
+ this.supportThreshold = supportThreshold;
+ }
+
+ public String getMethodDescription() {
+ return getSupportDescription(supportThreshold) + " greedy clustering";
+ }
+
+ /**
+ * One clade support.
+ */
+ static final class Support {
+ /**
+ * number of trees containing this clade.
+ */
+ private int nTreesWithClade;
+ /**
+ * Sum of branch length separating clade from the rest of taxa (in trees containing the clade).
+ */
+ private double sumBranches;
+
+ Support() {
+ sumBranches = 0.0;
+ nTreesWithClade = 0;
+ }
+
+ public final void add(double branch) {
+ sumBranches += branch;
+ ++nTreesWithClade;
+ }
+ }
+
+ private final boolean debug = false;
+
+ // debug
+ private String subTreeRep(Tree t, Node n, Node root) {
+
+ if (t.isExternal(n)) {
+ return t.getTaxon(n).getName();
+ }
+ StringBuilder b = new StringBuilder();
+ for (Node x : t.getAdjacencies(n)) {
+ if (x == root) continue;
+ if (b.length() > 0) b.append(",");
+ b.append(subTreeRep(t, x, n));
+ }
+ return '(' + b.toString() + ')';
+ }
+
+ private String tipsAsText(FixedBitSet b) {
+ String names = "(";
+ for (int i = b.nextOnBit(0); i >= 0; i = b.nextOnBit(i + 1)) {
+ names = names + taxons.get(i).getName() + ",";
+ }
+ return names + ")";
+ }
+
+ // Scan method:
+ //
+ // loop on all trees t
+ // establish support :
+ // for e : external nodes of t - add adjacencies of e to scan set and add e to done set
+ //
+ // while n in scan set:
+ // if all adj of n done - remove n from scan. add n to done. continue
+ // if all adj of n sans one are done:
+ // get set of subtree tips from done nodes
+ // add new split with branch length to support set
+ // add not done adj to scan list
+ // remove n from scan and add it to done list
+
+ public final Tree build() {
+
+ try {
+ Map<FixedBitSet, Support> support = new HashMap<FixedBitSet, Support>();
+ double[] sumBranchesOfExternal = new double[taxons.size()];
+
+ int nTree = 0;
+ for (Tree tree : trees) {
+ int initialCapacity = tree.getNodes().size();
+ Set<Node> scanSet = new LinkedHashSet<Node>(initialCapacity);
+ Map<Node, FixedBitSet> doneSet = new HashMap<Node, FixedBitSet>(initialCapacity);
+
+ for (Node n : tree.getExternalNodes()) {
+ FixedBitSet b = new FixedBitSet(nExternalNodes);
+ final int position = taxons.indexOf(tree.getTaxon(n));
+ b.set(position);
+
+ if (debug)
+ System.out.print(taxons.indexOf(tree.getTaxon(n)) + ":" + tree.getTaxon(n).getName() + " ");
+ doneSet.put(n, b);
+ for (Node a : tree.getAdjacencies(n)) {
+ scanSet.add(a);
+ }
+ sumBranchesOfExternal[position] += tree.getEdgeLength(n, tree.getAdjacencies(n).get(0));
+ }
+
+ int nInternalEdges = nExternalNodes - 3;
+
+ List<Node> intr = new ArrayList<Node>(tree.getInternalNodes());
+
+ if (debug) System.out.println("\ntree " + Utils.toNewick(Utils.rootTheTree(tree)));
+
+ while (scanSet.size() > 0) {
+ Set<Node> nextScanSet = new LinkedHashSet<Node>(initialCapacity);
+
+ for (Node n : scanSet) {
+ if (debug) System.out.println("scan " + intr.indexOf(n));
+ int nDone = 0;
+ List<Node> adjacencies = tree.getAdjacencies(n);
+ for (Node a : adjacencies) {
+ if (doneSet.containsKey(a)) ++nDone;
+ }
+
+ if (nDone + 1 < adjacencies.size()) {
+ if (debug) System.out.println("add to next " + intr.indexOf(n));
+ nextScanSet.add(n);
+ continue;
+ }
+
+ if (nDone < adjacencies.size()) {
+
+ FixedBitSet b = new FixedBitSet(nExternalNodes);
+ Node notDone = null;
+ for (Node a : adjacencies) {
+ if (doneSet.containsKey(a)) {
+ FixedBitSet subSet = doneSet.get(a);
+ if (subSet == null) {
+ if (debug) System.out.println(a + " " + subTreeRep(tree, n, notDone));
+ assert(false);
+ }
+ b.union(subSet);
+ } else {
+ notDone = a;
+ }
+ }
+
+ final double branch;
+
+ branch = tree.getEdgeLength(n, notDone);
+
+ doneSet.put(n, new FixedBitSet(b));
+ // in case it has been added by a previous node
+ nextScanSet.remove(n);
+ // support keys always contains the (arbitrary) tip 0
+ if (! b.contains(0)) {
+ b.complement();
+ }
+ Support s = support.get(b);
+ if (s == null) {
+ s = new Support();
+ support.put(b, s);
+ }
+ if (debug) {
+ System.out.println("add " + b + "<" + subTreeRep(tree, n, notDone) + ">"
+ + " " + s.nTreesWithClade + "/" + s.sumBranches + " " + branch);
+ }
+
+ s.add(branch);
+ --nInternalEdges;
+
+ if (debug) System.out.println("add to next " + intr.indexOf(notDone));
+
+ nextScanSet.add(notDone);
+ } else {
+ if (debug) {
+ for (Node x : tree.getAdjacencies(n)) {
+ System.out.println(subTreeRep(tree, x, n) + " is done " + intr.indexOf(n));
+ }
+ }
+ doneSet.put(n, null);
+ }
+ }
+
+ scanSet = nextScanSet;
+ }
+ if (debug) System.out.println(nInternalEdges);
+
+ ++nTree;
+ if( fireSetProgress((0.9 * nTree)/ trees.length) ) {
+ return null;
+ }
+ }
+
+ // sorts support from largest to smallest
+ final Comparator<Map.Entry<FixedBitSet, Support>> comparator = new Comparator<Map.Entry<FixedBitSet, Support>>() {
+ public int compare(Map.Entry<FixedBitSet, Support> o1, Map.Entry<FixedBitSet, Support> o2) {
+ return o2.getValue().nTreesWithClade - o1.getValue().nTreesWithClade;
+ }
+ };
+
+ // add everything to queue
+ PriorityQueue<Map.Entry<FixedBitSet, Support>> queue =
+ new PriorityQueue<Map.Entry<FixedBitSet, Support>>(support.size(), comparator);
+
+ for (Map.Entry<FixedBitSet, Support> s : support.entrySet()) {
+ queue.add(s);
+ }
+
+ MutableRootedTree consTree = new MutableRootedTree();
+
+ // Contains all internal nodes in the tree so far, ordered so descendants
+ // appear later than ancestors
+ List<Node> internalNodes = new ArrayList<Node>(nExternalNodes);
+
+ // For each internal node, a bit set with the complete set of tips for it's clade
+ List<FixedBitSet> internalNodesTips = new ArrayList<FixedBitSet>(nExternalNodes);
+ assert taxons.size() == nExternalNodes;
+
+ // establish a tree with one root having all tips as descendants
+ internalNodesTips.add(new FixedBitSet(nExternalNodes));
+ Node[] nodes = new Node[nExternalNodes];
+ for (int nt = 0; nt < taxons.size(); ++nt) {
+ nodes[nt] = consTree.createExternalNode(taxons.get(nt));
+ internalNodesTips.get(0).set(nt);
+ }
+
+ internalNodes.add(consTree.createInternalNode(Arrays.asList(nodes)));
+
+ while (queue.peek() != null) {
+ Map.Entry<FixedBitSet, Support> e = queue.poll();
+ final Support s = e.getValue();
+
+ final double psupport = (1.0 * s.nTreesWithClade) / trees.length;
+
+ if (psupport < supportThreshold) {
+ break;
+ }
+
+ final FixedBitSet splitTips = e.getKey();
+
+ if (debug) {
+ System.out.println(100.0 * psupport + " Split: " + splitTips + " "
+ + tipsAsText(splitTips) + "/" + tipsAsText(FixedBitSet.complement(splitTips)));
+ }
+
+ boolean found = false;
+
+ // locate the node containing the split. going in reverse order insures the lowest one is hit first
+ for (int nsub = internalNodesTips.size() - 1; nsub >= 0; --nsub) {
+ // size of intersection between tips & split
+ final int nSplit = internalNodesTips.get(nsub).intersectCardinality(splitTips);
+
+ FixedBitSet allNodeTips = internalNodesTips.get(nsub);
+ if (nSplit > 0 && nSplit < allNodeTips.cardinality()) {
+ // if split is actually with complement of arbitrary representation - use complement
+ FixedBitSet sharedTips = new FixedBitSet(allNodeTips);
+ sharedTips.intersect(splitTips);
+ if (! sharedTips.equals(splitTips)) {
+ sharedTips.complement();
+ sharedTips.intersect(allNodeTips);
+ if (! sharedTips.equals(FixedBitSet.complement(splitTips))) {
+ continue;
+ }
+ }
+
+ // Locate node descendants containing the split
+ found = true;
+ List<Integer> split = new ArrayList<Integer>();
+
+ Node n = internalNodes.get(nsub);
+ int l = 0;
+ List<Node> children = consTree.getChildren(n);
+ for (Node ch : children) {
+ if (consTree.isExternal(ch)) {
+ if (sharedTips.contains(taxons.indexOf(consTree.getTaxon(ch)))) {
+ split.add(l);
+ }
+ } else {
+ // internal
+ int o = internalNodes.indexOf(ch);
+ int i = internalNodesTips.get(o).intersectCardinality(sharedTips);
+ if (i == internalNodesTips.get(o).cardinality()) {
+ split.add(l);
+ } else if (i > 0) {
+ // Non compatible
+ found = false;
+ break;
+ }
+ }
+ ++l;
+ }
+
+
+ if (! (found && split.size() < children.size())) {
+ found = false;
+ break;
+ }
+
+ if (split.size() == 0) {
+ System.out.println("Bug??");
+ assert(false);
+ }
+
+ Node detached = consTree.detachChildren(n, split);
+ final double length = s.sumBranches / s.nTreesWithClade;
+ consTree.setLength(detached, length);
+
+ detached.setAttribute(getSupportAttributeName(), isSupportAsPercent() ? 100 * psupport : psupport);
+
+ if (debug) {
+ System.out.println("detached:" + subTreeRep(consTree, detached, n) + " len " + length + " sup " + psupport);
+ System.out.println("tree: " + Utils.toNewick(consTree));
+ }
+
+ // insert just after parent, so before any descendants
+ internalNodes.add(nsub + 1, detached);
+ internalNodesTips.add(nsub + 1, new FixedBitSet(sharedTips));
+
+ break;
+ }
+ }
+
+ if (psupport >= .5 && ! found) {
+ System.out.println("Bug??");
+ assert(false);
+ }
+ }
+
+ // establish length for tips
+ for (int nt = 0; nt < taxons.size(); ++nt) {
+ final Node n = consTree.getNode(taxons.get(nt));
+ consTree.setLength(n, sumBranchesOfExternal[nt] / trees.length);
+ }
+
+ if (outGroup != null) {
+ Node out = consTree.getNode(outGroup);
+ Set<String> a = new HashSet<String>();
+ a.add(getSupportAttributeName());
+ consTree.reRootWithOutgroup(out, a);
+ }
+
+ consTree.setConceptuallyUnrooted(true);
+
+ fireSetProgress(1.0);
+
+ return consTree;
+ } catch (Graph.NoEdgeException e) {
+ // bug
+ }
+ return null;
+ }
+}
diff --git a/src/jebl/evolution/trees/HashPair.java b/src/jebl/evolution/trees/HashPair.java
new file mode 100644
index 0000000..414a323
--- /dev/null
+++ b/src/jebl/evolution/trees/HashPair.java
@@ -0,0 +1,30 @@
+package jebl.evolution.trees;
+
+/**
+ * A pair suitable for use in a HashMap.
+ *
+ * @author Joseph Heled
+ *
+ * @version $Id: HashPair.java 544 2006-11-28 00:06:19Z twobeers $
+ */
+
+class HashPair<T> {
+ HashPair(T a, T b) {
+ first = a;
+ second = b;
+ }
+
+ public int hashCode() {
+ return first.hashCode() + second.hashCode();
+ }
+
+ public boolean equals(Object x) {
+ if( x instanceof HashPair ) {
+ return ((HashPair) x).first.equals(first) && ((HashPair )x).second.equals(second);
+ }
+ return false;
+ }
+
+ public final T first;
+ public final T second;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/MRCACConsensusTreeBuilder.java b/src/jebl/evolution/trees/MRCACConsensusTreeBuilder.java
new file mode 100644
index 0000000..05e5714
--- /dev/null
+++ b/src/jebl/evolution/trees/MRCACConsensusTreeBuilder.java
@@ -0,0 +1,301 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.util.FixedBitSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Construct a consensus tree for a set of rooted trees. The construction is done via clustering. For any two
+ * clusters/clades, their distance is the height of their most recent common ancesstor (computed as an average over all
+ * trees). This seem natural as it connectes clades in reverse time order.
+ *
+ * @author Joseph Heled
+ * @version $Id: MRCACConsensusTreeBuilder.java 616 2007-01-09 21:55:13Z pepster $
+ **/
+
+public class MRCACConsensusTreeBuilder extends ConsensusTreeBuilder<RootedTree> {
+ RootedTree[] trees;
+ private double supportThreshold;
+ private List<FixedBitSet> tipsInCluster;
+
+ private TreeInfo[] info;
+
+ private boolean debug = false;
+
+ public MRCACConsensusTreeBuilder(Tree[] trees, double supportThreshold) {
+ this(trees, supportThreshold, DEFAULT_SUPPORT_ATTRIBUTE_NAME, true);
+ }
+
+ public MRCACConsensusTreeBuilder(Tree[] trees, double supportThreshold, String supportAttributeName, boolean asPercent) {
+ super(trees, supportAttributeName, asPercent);
+ this.trees = new RootedTree[trees.length];
+ for(int i = 0; i < trees.length; ++i) {
+ this.trees[i] = (RootedTree)trees[i];
+ }
+ this.supportThreshold = supportThreshold;
+
+ info = new TreeInfo[trees.length];
+ for(int iTree = 0; iTree < trees.length; ++iTree) {
+ info[iTree] = new TreeInfo(this.trees[iTree]);
+ }
+ }
+
+ public RootedTree build() {
+ return earliestCommonAncestorClustering(supportThreshold);
+ }
+
+ public String getMethodDescription() {
+ return getSupportDescription(supportThreshold) + " MRCACC";
+ }
+
+
+ class TreeInfo {
+ // for each tree, establish a postorder order, and in each internal node the subsets of decendentants
+ FixedBitSet[] nodesTipSet;
+
+ int[] postorder;
+ int[][] nodeChildren;
+
+ private Node[] allNodes;
+ TreeInfo(RootedTree tree) {
+
+ allNodes = new Node[tree.getNodes().size()];
+
+ for( Node node : tree.getExternalNodes() ) {
+ int i = taxons.indexOf( tree.getTaxon(node) );
+ allNodes[i] = node;
+ }
+
+ int k = nExternalNodes;
+ for( Node node : tree.getInternalNodes() ) {
+ allNodes[k] = node;
+ ++k;
+ }
+
+ postorder = new int[allNodes.length - nExternalNodes];
+ nodesTipSet = new FixedBitSet[allNodes.length];
+ nodeChildren = new int [allNodes.length][];
+
+ inPostorder(tree, tree.getRootNode(), 0);
+ }
+
+ private int inPostorder(RootedTree tree, Node node, int nPost) {
+ List<Node> all = Arrays.asList(allNodes);
+ FixedBitSet b = new FixedBitSet(allNodes.length);
+ final int c = all.indexOf(node);
+ if( tree.isExternal(node) ) {
+ b.set(c);
+ } else {
+
+ List<Node> children = tree.getChildren(node);
+ nodeChildren[c] = new int[children.size()];
+ int nc = 0;
+ for( Node child : children ) {
+ nPost = inPostorder(tree, child, nPost);
+ int c1 = all.indexOf(child);
+ nodeChildren[c][nc] = c1;
+ ++nc;
+ b.union(nodesTipSet[c1]);
+ }
+ postorder[nPost] = c;
+ ++nPost;
+ }
+ nodesTipSet[c] = b;
+ return nPost;
+ }
+ }
+
+ // Height of minimal clade containing clusters i&j (i < j) is height[i][j].
+ // (Only upper right of matrix is used)
+ double[][] height;
+
+ void setupPairs() {
+ height = new double [nExternalNodes][nExternalNodes];
+
+ if( debug ) {
+ for(int k = 0; k < taxons.size(); ++k) {
+ System.out.print(taxons.get(k).getName() + ":" + k + ", ");
+ }
+ System.out.println();
+ }
+
+ for(int iTree = 0; iTree < trees.length; ++iTree) {
+ if( debug ) System.out.println("tree " + Utils.toNewick(trees[iTree]));
+
+ TreeInfo info = this.info[iTree];
+ for (final int nodeIndex : info.postorder) {
+ final int leftChildIndex = info.nodeChildren[nodeIndex][0];
+ final int rightChildIndex = info.nodeChildren[nodeIndex][1];
+ final FixedBitSet left = info.nodesTipSet[leftChildIndex];
+ final FixedBitSet right = info.nodesTipSet[rightChildIndex];
+
+ for (int lTip = left.nextOnBit(0); lTip >= 0; lTip = left.nextOnBit(lTip + 1)) {
+ for (int rTip = right.nextOnBit(0); rTip >= 0; rTip = right.nextOnBit(rTip + 1)) {
+
+ final double h = trees[iTree].getHeight(info.allNodes[nodeIndex]);
+ height[Math.min(lTip, rTip)][Math.max(lTip, rTip)] += h;
+ }
+ }
+ }
+ }
+
+ for(int d = 0; d < nExternalNodes; ++d) {
+ for(int d1 = d+1; d1 < nExternalNodes; ++d1) {
+ height[d][d1] /= trees.length;
+ }
+ }
+ }
+
+ private double[] updateHeights(int i, int j) {
+ final int nClusters = tipsInCluster.size();
+ double[] distances = new double[nClusters +1];
+
+ // tips in clustes i & j
+ FixedBitSet joined = new FixedBitSet(tipsInCluster.get(i));
+ joined.union(tipsInCluster.get(j));
+
+ int numberOfTreesSupportClade = 0;
+
+ if( nClusters == 2 ) {
+ for (RootedTree tree : trees) {
+ distances[1] += tree.getHeight(tree.getRootNode());
+ }
+ distances[2] = 1;
+ } else {
+ for(int l = 0; l < nClusters; ++l) {
+ if( (l == i || l == j) ) {
+ continue;
+ }
+
+ // Tips in i, j & l
+ FixedBitSet joinedWithL = new FixedBitSet(joined);
+ joinedWithL.union(tipsInCluster.get(l));
+
+ for(int iTree = 0; iTree < trees.length; ++iTree) {
+ RootedTree tree = trees[iTree];
+ TreeInfo info = this.info[iTree];
+ boolean commonAnncestorFound = false;
+
+ for (int nodeIndex : info.postorder) {
+ FixedBitSet nodeBS = info.nodesTipSet[nodeIndex];
+
+ if (!commonAnncestorFound && joined.setInclusion(nodeBS)) {
+
+ final int tipsInSubtree = nodeBS.cardinality();
+ final int tipsInClusters = joined.cardinality();
+
+ numberOfTreesSupportClade += ((tipsInSubtree == tipsInClusters) ? 1 : 0);
+ commonAnncestorFound = true;
+ }
+
+ if( joinedWithL.setInclusion(nodeBS) ) {
+ distances[l] += tree.getHeight(info.allNodes[nodeIndex]);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for(int l = 0; l < nClusters; ++l) {
+ distances[l] /= trees.length;
+ }
+
+ distances[nClusters] = (double)numberOfTreesSupportClade / trees.length;
+ return distances;
+ }
+
+ private RootedTree earliestCommonAncestorClustering(double supportThreshold) {
+
+ MutableRootedTree consensus = new MutableRootedTree();
+ List<Node> subTrees = new ArrayList<Node>(nExternalNodes);
+
+ // compute average length of branch from tip over all trees
+ double[] tipHeights = new double[taxons.size()];
+ for (RootedTree tree : trees) {
+ for (Node e : tree.getExternalNodes()) {
+ final int i = taxons.indexOf(tree.getTaxon(e));
+ tipHeights[i] += tree.getHeight(e);
+ }
+ }
+
+ // Start with each tip in it's own cluster
+ tipsInCluster = new ArrayList<FixedBitSet>(nExternalNodes);
+ for(int k = 0; k < nExternalNodes; ++k) {
+ FixedBitSet b = new FixedBitSet(nExternalNodes);
+ b.set(k);
+ tipsInCluster.add(b);
+ final Node externalNode = consensus.createExternalNode(taxons.get(k));
+ consensus.setHeight(externalNode, tipHeights[k]/trees.length);
+ subTrees.add(externalNode);
+ }
+
+ // set up distances between all tips
+ setupPairs();
+
+ for(int nClusters = nExternalNodes; nClusters > 1; --nClusters) {
+
+ // Find most recent ancesstor of two clusters from distance matrix
+ double mostRecentAnncestorHeight = Double.MAX_VALUE;
+ int besti = 0, bestj = 0;
+ for(int d = 0; d < nClusters; ++d) {
+ for(int d1 = d+1; d1 < nClusters; ++d1) {
+ if( height[d][d1] < mostRecentAnncestorHeight ) {
+ mostRecentAnncestorHeight = height[d][d1];
+ besti = d; bestj = d1;
+ }
+ }
+ }
+
+ // Join clusters
+ double[] newHeights = updateHeights(besti, bestj);
+ double supportForClade = newHeights[newHeights.length-1];
+
+ final Node[] children = {subTrees.get(besti), subTrees.get(bestj)};
+ final double maxChild = Math.max(consensus.getHeight(children[0]), consensus.getHeight(children[1]));
+ mostRecentAnncestorHeight = Math.max(mostRecentAnncestorHeight, maxChild);
+
+ final Node sub = consensus.createInternalNode(Arrays.asList(children));
+
+ consensus.setHeight(sub, mostRecentAnncestorHeight);
+ // root always 100%
+ if( nClusters > 2 ) {
+ sub.setAttribute(getSupportAttributeName(), isSupportAsPercent() ? 100 * supportForClade : supportForClade);
+ }
+ subTrees.set(besti, sub);
+ tipsInCluster.get(besti).union(tipsInCluster.get(bestj));
+ tipsInCluster.remove(bestj);
+ subTrees.remove(bestj);
+
+ // compress distance matrix
+ for(int i = 0; i < nClusters; ++i) {
+ if( besti < i ) {
+ height[besti][i] = newHeights[i];
+ } else {
+ height[i][besti] = newHeights[i];
+ }
+ }
+
+ for(int i = 0; i < nClusters-1; ++i) {
+ for(int j = bestj; j < nClusters-1; ++j) {
+ height[i][j] = height[i + (i >=bestj ? 1 : 0)][j+1];
+ }
+ }
+ }
+
+ // Remove nodes with low support
+ if (isSupportAsPercent()) {
+ supportThreshold *= 100;
+ }
+ for( Node node : consensus.getInternalNodes() ) {
+ Object sup = node.getAttribute(getSupportAttributeName());
+ if( sup != null && (Double)sup < supportThreshold ) {
+ consensus.removeInternalNode(node);
+ }
+ }
+ return consensus;
+ }
+}
diff --git a/src/jebl/evolution/trees/MostProbableTopology.java b/src/jebl/evolution/trees/MostProbableTopology.java
new file mode 100644
index 0000000..ebe6ca1
--- /dev/null
+++ b/src/jebl/evolution/trees/MostProbableTopology.java
@@ -0,0 +1,472 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.io.NexusExporter;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.FixedBitSet;
+
+import java.util.*;
+
+/**
+ *
+ * Given a set of trees determine the most probable trees, i.e. the most frequent topologies.
+ * Set branch lengths / node heights from set conditional on topology.
+ *
+ * @author Joseph Heled
+ * @version $Id: MostProbableTopology.java 626 2007-01-14 20:30:21Z pepster $
+ */
+public class MostProbableTopology {
+ /**
+ * Set of input trees
+ */
+ final List<Tree> trees;
+
+ /**
+ * Rooted or non-rooted set
+ */
+ final boolean rootedSet;
+
+ /**
+ * Common taxa for all trees. Order in this array is used as the common base for the trees.
+ */
+ final private List<Taxon> taxa;
+
+ final String consAttributeName = GreedyUnrootedConsensusTreeBuilder.DEFAULT_SUPPORT_ATTRIBUTE_NAME;
+
+ /**
+ *
+ * @param trees
+ */
+ public MostProbableTopology(Collection<? extends Tree> trees) {
+ this.trees = new ArrayList<Tree>(trees);
+ Tree tree0 = this.trees.get(0);
+ rootedSet = tree0 instanceof RootedTree && !((RootedTree)tree0).conceptuallyUnrooted();
+ taxa = new ArrayList<Taxon>(trees.iterator().next().getTaxa());
+ }
+
+ /**
+ * Entry for each unique topology in the set
+ */
+ private class TopologyEntry {
+ // number of trees with this topology
+ int count;
+
+ // Index of one tree with topology
+ int representativeIndex;
+
+ /**
+ *
+ * @param nTree index of representative tree
+ */
+ public TopologyEntry(int nTree) {
+ representativeIndex = nTree;
+ count = 1;
+ }
+ }
+
+ /**
+ * Callback for iterator over nodes of a rooted tree
+ */
+ private interface NodeCallback {
+ /**
+ *
+ * @param node visited node
+ * @param tipSet Taxa in sub-tree beneath node. A set bit in the n'th position means taxa[n] is in.
+ */
+ void visit(Node node, FixedBitSet tipSet);
+ }
+
+ /**
+ * Callback for iterator over edges of an unrooted tree
+ */
+ private interface EdgeCallback {
+ /**
+ *
+ * @param edge visited edge
+ * @param tipSet Taxa in one bi-partition when this edge is deleted.
+ * A set bit in the n'th position means taxa[n] is in. Normalized so that the first (0'th) tip is always in.
+ */
+ void visit(Edge edge, FixedBitSet tipSet);
+ }
+
+ /**
+ * Branch length / Node height data collected from the set of trees.
+ *
+ * Simply the average of the value in trees containing the sub-tree / partition.
+ */
+ private class ConditionalData {
+ // accumulating sum
+ double hSum;
+
+ // number of trees contributing to sum
+ int count;
+
+ public ConditionalData() {
+ hSum = 0.0;
+ count = 0;
+ }
+
+ // add one more
+ public void add(double v) {
+ hSum += v;
+ ++count;
+ }
+
+ // get current estimate
+ public double length() {
+ assert( count > 0);
+ return hSum / count;
+ }
+ }
+
+ /**
+ * Entry for one probable tree.
+ *
+ * Used to collect conditional estimate of branch/node data from the set of trees.
+ * Base for rooted/unrooted cases.
+ */
+ private interface Info {
+ // Get the tree
+ Tree getTree();
+
+ // Using accumalated data, set branches/heights
+ void setBranches();
+ }
+
+ /**
+ * Helper in traversing unrooted trees.
+ */
+ private class TraversableTree {
+ // the tree
+ final Tree t;
+
+ TraversableTree(Tree t) {
+ this.t = t;
+ }
+
+ // Traberse the tree, calling call.visit for every edge.
+ void traverse(EdgeCallback call) {
+ final Node tip = t.getExternalNodes().iterator().next();
+ traverse(call, t.getAdjacencies(tip).get(0), tip );
+ }
+
+ // Traverse all edges in partition containing n when edge (n, root) is removed.
+
+ private FixedBitSet traverse(EdgeCallback call, Node n, Node root) {
+ final FixedBitSet tipSet = new FixedBitSet(taxa.size());
+
+ if( t.isExternal(n) ) {
+ tipSet.set( taxa.indexOf( t.getTaxon(n) ) );
+ } else {
+ for( Node c : t.getAdjacencies(n) ) {
+ if( c == root ) continue;
+ final FixedBitSet cTips = traverse(call, c, n);
+ tipSet.union(cTips);
+ }
+ }
+
+ final boolean needComplement = !tipSet.contains(0);
+ if( needComplement ) {
+ tipSet.complement();
+ }
+
+ try {
+ call.visit( t.getEdge(n, root), tipSet);
+ } catch (Graph.NoEdgeException e) {
+ assert false;
+ }
+
+ // we can't simply flip back because map does not copy it's key.
+ // second time I forget this
+ if( needComplement ) {
+ final FixedBitSet b = new FixedBitSet(tipSet);
+ b.complement();
+ return b;
+ }
+ return tipSet;
+ }
+ }
+
+ private class UnrootedTreeInfo extends TraversableTree implements Info {
+ public Tree getTree() {
+ return t;
+ }
+
+ /**
+ * Data for one edge
+ */
+ class EdgeInfo extends ConditionalData {
+ Edge e;
+
+ public EdgeInfo(Edge e) {
+ super();
+ this.e = e;
+ }
+ }
+
+ // All edges, indexed the normalized partition when edge is removed (normalized == containing tip
+ // of taxa[0].
+
+ final public Map<FixedBitSet, EdgeInfo> m;
+
+ public UnrootedTreeInfo(SimpleTree t) {
+ super(t);
+ m = new HashMap<FixedBitSet, EdgeInfo>();
+ traverse(new EdgeCallback() {
+ public void visit(Edge e, FixedBitSet tipSet) {
+ m.put(tipSet, new EdgeInfo(e));
+ }
+ } );
+ }
+
+ public void setBranches() {
+ traverse(new EdgeCallback() {
+ public void visit(Edge e, FixedBitSet tipSet) {
+ final EdgeInfo info = m.get(tipSet); assert(info != null);
+ final double h = info.length();
+
+ ((SimpleTree)t).setEdgeLength(info.e, h);
+
+ final double support = (100.0 * info.count) / trees.size();
+ info.e.setAttribute(consAttributeName, support);
+ }
+ } );
+ }
+ }
+
+ /**
+ * Helper in traversing rooted trees.
+ */
+ class TraversableRootedTree {
+ final public RootedTree t;
+
+ TraversableRootedTree(RootedTree t) {
+ this.t = t;
+ }
+
+ // Traberse the tree, calling call.visit for every node.
+ void traverse(NodeCallback call) {
+ traverse(call, t.getRootNode());
+ }
+
+ // Traverse the subtree below n
+ private FixedBitSet traverse(NodeCallback call, Node n) {
+ final FixedBitSet tipSet = new FixedBitSet(taxa.size());
+
+ if( t.isExternal(n) ) {
+ tipSet.set( taxa.indexOf( t.getTaxon(n) ) );
+ } else {
+ for( Node c : t.getChildren(n) ) {
+ final FixedBitSet cTips = traverse(call, c);
+ tipSet.union(cTips);
+ }
+ }
+ call.visit(n, tipSet);
+ return tipSet;
+ }
+ }
+
+ private class TreeInfo extends TraversableRootedTree implements Info {
+ /**
+ * Data for one node.
+ */
+ class NodeInfo extends ConditionalData {
+ Node n;
+
+ public NodeInfo(Node n) {
+ super();
+ this.n = n;
+ }
+ }
+
+ // All nodes, indexed the set of tips in subtree below node
+ final public Map<FixedBitSet, NodeInfo> m;
+
+ TreeInfo(SimpleRootedTree t) {
+ super(t);
+ m = new HashMap<FixedBitSet, NodeInfo>();
+ traverse(new NodeCallback() {
+ public void visit(Node n, FixedBitSet tipSet) {
+ m.put(tipSet, new NodeInfo(n));
+ }
+ } );
+ }
+
+ public Tree getTree() {
+ return t;
+ }
+
+ public void setBranches() {
+ traverse(new NodeCallback() {
+ public void visit(Node n, FixedBitSet tipSet) {
+ final NodeInfo info = m.get(tipSet); assert(info != null);
+ double h = info.length();
+ for( Node c : t.getChildren(info.n) ) {
+ final double ch = t.getHeight(c);
+ if( ch > h ) {
+ h = ch;
+ }
+ }
+ ((SimpleRootedTree)t).setHeight(info.n, h);
+
+ info.n.setAttribute(consAttributeName,
+ (100.0 * info.count) / trees.size());
+ }
+ } );
+ }
+ }
+
+ /**
+ * Get the most probable tree(s)
+ *
+ * @param max At most this number of trees (max <= 0 is ignored)
+ * @param threshold (in [01]) return first K topologies whose total frequencey is greater that threshold.
+ * @return probable trees
+ */
+ public List<Tree> get(final int max, final double threshold) {
+ final int nTrees = trees.size();
+ // Generate a "standard" representation for each tree topology. For rooted trees this is the newick format
+ // (leaving out any branch information), where the children at each node are sorted.
+ // Unrooted trees are rooted at the internal node connected to the first tip (taxa[0]) and the rooted
+ // method is applied to that.
+
+ Map<String, TopologyEntry> m = new HashMap<String, TopologyEntry>(nTrees);
+ for(int nTree = 0; nTree < nTrees; ++ nTree) {
+ final Tree t = trees.get(nTree);
+ final String rep = standardTopologyRepresentation(t);
+
+ TopologyEntry e = m.get(rep);
+ if( e == null ) {
+ m.put(rep, new TopologyEntry(nTree));
+ } else {
+ e.count += 1;
+ }
+ }
+
+ // sorts support from largest to smallest
+ final Comparator<Map.Entry<String, TopologyEntry>> comparator = new Comparator<Map.Entry<String, TopologyEntry>>() {
+ public int compare(Map.Entry<String, TopologyEntry> o1, Map.Entry<String, TopologyEntry> o2) {
+ return o2.getValue().count - o1.getValue().count;
+ }
+ };
+
+ // add everything to queue
+ PriorityQueue<Map.Entry<String, TopologyEntry>> queue =
+ new PriorityQueue<Map.Entry<String, TopologyEntry>>(m.size(), comparator);
+
+ for (Map.Entry<String, TopologyEntry> s : m.entrySet()) {
+ queue.add(s);
+ }
+
+ // collect candidates
+ final List<Info> candidates = new ArrayList<Info>();
+
+ //final int th = threshold > 0 ? (int)(threshold * nTrees) : 1;
+ final int th = (int)(threshold * nTrees);
+
+ while (queue.peek() != null && candidates.size() <= th && !(max > 0 && candidates.size() >= max) ) {
+ Map.Entry<String, TopologyEntry> e = queue.poll();
+ final MostProbableTopology.TopologyEntry info = e.getValue();
+
+ // make a copy
+ final Tree tree = trees.get(info.representativeIndex);
+
+ Info candidate;
+ if( rootedSet ) {
+ final SimpleRootedTree r = new SimpleRootedTree((RootedTree) tree);
+ candidate = new TreeInfo(r);
+ } else {
+ final SimpleTree t = new SimpleTree(tree);
+ candidate = new UnrootedTreeInfo(t);
+ }
+
+ candidates.add(candidate);
+ final Tree tree1 = candidate.getTree();
+ tree1.setAttribute("Frequency", (100.0 * info.count) / nTrees);
+ tree1.setAttribute(NexusExporter.treeNameAttributeKey, "topology_" + candidates.size());
+ }
+
+ // Now go over the set of trees, and for each node/edge record the value in all
+ // candidate trees containng that node/edge.
+
+ for(int nTree = 0; nTree < nTrees; ++ nTree) {
+ if( rootedSet ) {
+ final RootedTree t = (RootedTree)trees.get(nTree);
+ new TraversableRootedTree(t).traverse(new NodeCallback() {
+ public void visit(Node n, FixedBitSet tipSet) {
+ final double height = t.getHeight(n);
+ for( Info ti : candidates ) {
+ final TreeInfo.NodeInfo ni = ((TreeInfo)ti).m.get(tipSet);
+
+ if( ni != null ) {
+ ni.add(height);
+ }
+ }
+ }
+ } );
+ } else {
+
+ final Tree t = trees.get(nTree);
+ new TraversableTree(t).traverse(new EdgeCallback() {
+ public void visit(Edge e, FixedBitSet tipSet) {
+ final double length = e.getLength();
+ for( Info ti : candidates ) {
+ final UnrootedTreeInfo.EdgeInfo ei = ((UnrootedTreeInfo)ti).m.get(tipSet);
+
+ if( ei != null ) {
+ ei.add(length);
+ }
+ }
+ }
+ } );
+ }
+ }
+
+ // Set heights/lengths from accumulated information
+ List<Tree> results = new ArrayList<Tree>();
+ for (final Info info : candidates) {
+ info.setBranches();
+
+ results.add(info.getTree());
+ }
+ return results;
+ }
+
+ // "Standard" string representation
+ private String standardTopologyRepresentation(Tree t) {
+ if( t instanceof RootedTree ) {
+ final RootedTree r = (RootedTree) t;
+ return standardTop(r, r.getRootNode());
+ }
+
+ // unrooted tree. Root at internal node near first taxa, call rooted method
+ final List<Node> adj = t.getAdjacencies(t.getNode(taxa.get(0))); assert( adj.size() == 1 );
+ final RootedTree r = new RootedFromUnrooted(t, adj.get(0), true);
+ return standardTop(r, r.getRootNode());
+ }
+
+ // "Standard" string representation of a rooted tree
+ // Newick for taxa only, where children are sorted.
+ private String standardTop(RootedTree t, Node n) {
+ if( t.isExternal(n) ) {
+ return Integer.toString(taxa.indexOf( t.getTaxon(n) ));
+ }
+ final List<Node> dec = t.getChildren(n);
+ final String[] strings = new String[dec.size()];
+ for(int k = 0; k < dec.size(); ++k) {
+ strings[k] = standardTop(t, dec.get(k));
+ }
+ final List<String> list = Arrays.asList(strings);
+ Collections.sort(list);
+ StringBuilder sb = new StringBuilder();
+
+ for( String s : strings ) {
+ sb.append(sb.length() == 0 ? '(' : ",");
+ sb.append(s);
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+}
diff --git a/src/jebl/evolution/trees/MutableRootedTree.java b/src/jebl/evolution/trees/MutableRootedTree.java
new file mode 100644
index 0000000..522199d
--- /dev/null
+++ b/src/jebl/evolution/trees/MutableRootedTree.java
@@ -0,0 +1,795 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.AttributableHelper;
+
+import java.util.*;
+
+/**
+ * A simple rooted tree providing some ability to manipulate the tree.
+ *
+ * - Root an unrooted tree using an outgroup.
+ * - Remove internal node: all children of node are adopted by it's parent.
+ * - Split/Refine node by creating two new children and distributing the children to new nodes.
+ * - Re-root a rooted tree given an outgroup.
+
+ * @author Joseph Heled
+ * @version $Id: MutableRootedTree.java 627 2007-01-15 03:50:40Z pepster $
+ *
+ */
+
+public class MutableRootedTree implements RootedTree {
+ MutableRootedTree() { super(); }
+
+ /**
+ * Construct a rooted tree from unrooted.
+ *
+ * @param tree Unrooted tree to root
+ * @param outGroup Node in tree assumed to be the outgroup
+ */
+ public MutableRootedTree(Tree tree, Node outGroup) {
+ if( ! tree.isExternal(outGroup) ) throw new IllegalArgumentException("Outgroup must be a tip");
+
+ // Adjacency of node to become new root.
+ Node root = tree.getAdjacencies(outGroup).get(0);
+
+ try {
+ MutableRootedNode newSubtreeRoot = rootAdjaceincesWith(tree, root, outGroup);
+
+
+ // Add the outgroup in
+ MutableRootedNode out = (MutableRootedNode)createExternalNode( tree.getTaxon(outGroup) );
+ setLength(out, tree.getEdgeLength(outGroup, root));
+ // Create new root
+ ArrayList<MutableRootedNode> rootChildren = new ArrayList<MutableRootedNode>();
+ rootChildren.add(out);
+ rootChildren.add(newSubtreeRoot);
+ //MutableRootedNode newRoot =
+ this.createInternalNode( rootChildren );
+ setLength(newSubtreeRoot,0);
+ } catch (NoEdgeException e) {
+ // bug
+ }
+ }
+
+
+ /**
+ * Remove internal node. Move all children to their grandparent.
+ * @param node to be removed
+ */
+ public void removeInternalNode(Node node) {
+ assert ! isExternal(node) && getRootNode() != node;
+
+ MutableRootedNode parent = (MutableRootedNode)getParent(node);
+ for( Node n : getChildren(node) ) {
+ parent.addChild((MutableRootedNode)n);
+ }
+ parent.removeChild(node);
+ internalNodes.remove(node);
+ }
+
+ /**
+ *
+ * @param node Node to refine
+ * @param leftSet indices of children in the left new subtree.
+ */
+ public void refineNode(Node node, int[] leftSet) {
+ List<Node> allChildren = getChildren(node);
+
+ List<Node> left = new ArrayList<Node>();
+ List<Node> right = new ArrayList<Node>();
+
+ for( int n : leftSet ) {
+ left.add(allChildren.get(n));
+ }
+ for( Node n : allChildren ) {
+ if( !left.contains(n) ) {
+ right.add(n);
+ }
+ }
+ internalNodes.remove(node);
+ MutableRootedNode saveRoot = rootNode;
+
+ MutableRootedNode lnode = (left.size() > 1) ? createInternalNode(left) : (MutableRootedNode)left.get(0);
+ MutableRootedNode rnode = (right.size() > 1) ? createInternalNode(right) : (MutableRootedNode)right.get(0);
+
+ List<MutableRootedNode> nodes = new ArrayList<MutableRootedNode>(2);
+ nodes.add(lnode);
+ nodes.add(rnode);
+ ((MutableRootedNode)node).replaceChildren(nodes);
+
+ rootNode = saveRoot;
+ }
+
+ /**
+ * Re-root tree using an outgroup.
+ * @param outGroup
+ * @param attributeNames Move those attributes (if they exist in node) to their previous parent. The idea is to
+ * preserve "branch" attributes which we now store in the child since only "node" properties are supported.
+ */
+ public void reRootWithOutgroup(Node outGroup, Set<String> attributeNames) {
+ assert isExternal(outGroup);
+ reRoot((MutableRootedNode)getAdjacencies(outGroup).get(0), attributeNames);
+ }
+
+ /**
+ * Construct a rooted sub-tree from unrooted. Done recursivly: Given an internal node N and one adjacency A to become
+ * the new parent, recursivly create subtrees for all adjacencies of N (ommiting A) using N as parent, and return
+ * an internal node with all subtrees as children. A tip simply creates an external node and returns it.
+ *
+ * @param tree Unrooted source tree
+ * @param node span sub-tree from this node
+ * @param parent adjacency of node which serves as the parent.
+ * @return rooted subtree.
+ * @throws NoEdgeException
+ */
+ private MutableRootedNode rootAdjaceincesWith(Tree tree, Node node, Node parent) throws NoEdgeException {
+ if( tree.isExternal(node) ) {
+ return (MutableRootedNode)createExternalNode( tree.getTaxon(node) );
+ }
+
+ List<Node> children = new ArrayList<Node>();
+ for( Node adj : tree.getAdjacencies(node) ) {
+ if( adj == parent ) continue;
+ MutableRootedNode rootedAdj = rootAdjaceincesWith(tree, adj, node);
+ setLength(rootedAdj, tree.getEdgeLength(adj, node));
+ children.add(rootedAdj);
+ }
+ return createInternalNode(children);
+ }
+
+ /**
+ * Similar to rootAdjaceincesWith.
+ * @param node
+ * @param attributeNames
+ */
+ private void reRoot(MutableRootedNode node, Set<String> attributeNames) {
+ MutableRootedNode parent = (MutableRootedNode)getParent(node);
+ if( parent == null) {
+ return;
+ }
+ double len = getLength(node);
+ parent.removeChild(node);
+ reRoot(parent, attributeNames);
+ if( parent == getRootNode() ) {
+ rootNode = node;
+ }
+
+ if( parent.getChildren().size() == 1 ) {
+ parent = (MutableRootedNode)parent.getChildren().get(0);
+ len += parent.getLength();
+ }
+
+ node.addChild(parent);
+ parent.setLength(len);
+ node.setParent(null);
+
+ if( attributeNames != null ) {
+ for( String name : attributeNames ) {
+ Object s = node.getAttribute(name);
+ if( s != null ) {
+ parent.setAttribute(name, s);
+ node.removeAttribute(name);
+ }
+ }
+ }
+ }
+
+ public Node detachChildren(Node node, List<Integer> split) {
+ assert( split.size() > 1 );
+
+ List<Node> allChildren = getChildren(node);
+
+ List<Node> detached = new ArrayList<Node>();
+
+ for( int n : split ) {
+ detached.add(allChildren.get(n));
+ }
+
+ MutableRootedNode saveRoot = rootNode;
+
+ for( Node n : allChildren ) {
+ if( detached.contains(n) ) {
+ ((MutableRootedNode)node).removeChild(n);
+ }
+ }
+
+ MutableRootedNode dnode = createInternalNode(detached);
+ ((MutableRootedNode)node).addChild(dnode);
+
+ rootNode = saveRoot;
+
+ return dnode;
+ }
+
+ /**
+ * Creates a new external node with the given taxon. See createInternalNode
+ * for a description of how to use these methods.
+ * @param taxon the taxon associated with this node
+ * @return the created node reference
+ */
+ public Node createExternalNode(Taxon taxon) {
+ MutableRootedNode node = new MutableRootedNode(taxon);
+ externalNodes.put(taxon, node);
+ return node;
+ }
+
+ /**
+ * Once a SimpleRootedTree has been created, the node stucture can be created by
+ * calling createExternalNode and createInternalNode. First of all createExternalNode
+ * is called giving Taxon objects for the external nodes. Then these are put into
+ * sets and passed to createInternalNode to create a parent of these nodes. The
+ * last node created using createInternalNode is automatically the root so when
+ * all the nodes are created, the tree is complete.
+ *
+ * @param children the child nodes of this nodes
+ * @return the created node reference
+ */
+ public MutableRootedNode createInternalNode(List<? extends Node> children) {
+ MutableRootedNode node = new MutableRootedNode(children);
+
+ for (Node child : children) {
+ ((MutableRootedNode)child).setParent(node);
+ }
+
+ internalNodes.add(node);
+
+ rootNode = node;
+ return node;
+ }
+
+
+ /**
+ * @param node the node whose height is being set
+ * @param height the height
+ */
+ public void setHeight(Node node, double height) {
+ lengthsKnown = false;
+ heightsKnown = true;
+
+ // If a single height of a single node is set then
+ // assume that all nodes have heights and by extension,
+ // branch lengths as well as these will be calculated
+ // from the heights
+ hasLengths = true;
+ hasHeights = true;
+
+ ((MutableRootedNode)node).setHeight(height);
+ }
+
+ /**
+ * @param node the node whose branch length (to its parent) is being set
+ * @param length the length
+ */
+ public void setLength(Node node, double length) {
+ heightsKnown = false;
+ lengthsKnown = true;
+
+ // If a single length of a single branch is set then
+ // assume that all branch have lengths and by extension,
+ // node heights as well as these will be calculated
+ // from the lengths
+ hasLengths = true;
+ hasHeights = true;
+
+ ((MutableRootedNode)node).setLength(length);
+ }
+
+ /**
+ * @param node the node whose children are being requested.
+ * @return the list of nodes that are the children of the given node.
+ * The list may be empty for a terminal node (a tip).
+ */
+ public List<Node> getChildren(Node node) {
+ return new ArrayList<Node>(((MutableRootedNode)node).getChildren());
+ }
+
+ /**
+ * @return Whether this tree has node heights available
+ */
+ public boolean hasHeights() {
+ return hasHeights;
+ }
+
+ /**
+ * @param node the node whose height is being requested.
+ * @return the height of the given node. The height will be
+ * less than the parent's height and greater than it children's heights.
+ */
+ public double getHeight(Node node) {
+ if (!hasHeights) throw new IllegalArgumentException("This tree has no node heights");
+ if (!heightsKnown) calculateNodeHeights();
+ return ((MutableRootedNode)node).getHeight();
+ }
+
+ /**
+ * @return Whether this tree has branch lengths available
+ */
+ public boolean hasLengths() {
+ return hasLengths;
+ }
+
+ /**
+ * @param node the node whose branch length (to its parent) is being requested.
+ * @return the length of the branch to the parent node (0.0 if the node is the root).
+ */
+ public double getLength(Node node) {
+ if (!hasLengths) throw new IllegalArgumentException("This tree has no branch lengths");
+ if (!lengthsKnown) calculateBranchLengths();
+ return ((MutableRootedNode)node).getLength();
+ }
+
+ /**
+ * @param node the node whose parent is requested
+ * @return the parent node of the given node, or null
+ * if the node is the root node.
+ */
+ public Node getParent(Node node) {
+ return ((MutableRootedNode)node).getParent();
+ }
+
+ /**
+ * The root of the tree has the largest node height of
+ * all nodes in the tree.
+ *
+ * @return the root of the tree.
+ */
+ public Node getRootNode() {
+ return rootNode;
+ }
+
+ public boolean isRoot(Node node) {
+ return node == rootNode;
+ }
+
+ /**
+ * @return a set of all nodes that have degree 1.
+ * These nodes are often refered to as 'tips'.
+ */
+ public Set<Node> getExternalNodes() {
+ return new HashSet<Node>(externalNodes.values());
+ }
+
+ /**
+ * @return a set of all nodes that have degree 2 or more.
+ * These nodes are often refered to as internal nodes.
+ */
+ public Set<Node> getInternalNodes() {
+ return new HashSet<Node>(internalNodes);
+ }
+
+ /**
+ * @return the set of taxa associated with the external
+ * nodes of this tree. The size of this set should be the
+ * same as the size of the external nodes set.
+ */
+ public Set<Taxon> getTaxa() {
+ return new HashSet<Taxon>(externalNodes.keySet());
+ }
+
+ /**
+ * @param node the node whose associated taxon is being requested.
+ * @return the taxon object associated with the given node, or null
+ * if the node is an internal node.
+ */
+ public Taxon getTaxon(Node node) {
+ return ((MutableRootedNode)node).getTaxon();
+ }
+
+ /**
+ * @param node the node
+ * @return true if the node is of degree 1.
+ */
+ public boolean isExternal(Node node) {
+ return ((MutableRootedNode)node).getChildren().size() == 0;
+ }
+
+ /**
+ * @param taxon the taxon
+ * @return the external node associated with the given taxon, or null
+ * if the taxon is not a member of the taxa set associated with this tree.
+ */
+ public Node getNode(Taxon taxon) {
+ return externalNodes.get(taxon);
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ MutableRootedNode node = (MutableRootedNode)externalNodes.get(from);
+ node.setTaxa(to);
+ }
+
+ /**
+ * Returns a list of edges connected to this node
+ *
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Edge> getEdges(Node node) {
+ List<Edge> edges = new ArrayList<Edge>();
+ for (Node adjNode : getAdjacencies(node)) {
+ edges.add(((MutableRootedNode)adjNode).getEdge());
+
+ }
+ return edges;
+ }
+
+ /**
+ * Returns an array of 2 nodes which are the nodes at either end of the edge.
+ *
+ * @param edge
+ * @return an array of 2 edges
+ */
+ public Node[] getNodes(Edge edge) {
+ for (Node node : getNodes()) {
+ if (((MutableRootedNode)node).getEdge() == edge) {
+ return new Node[] { node, ((MutableRootedNode)node).getParent() };
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Node> getAdjacencies(Node node) {
+ return ((MutableRootedNode)node).getAdjacencies();
+ }
+
+ /**
+ * Returns the Edge that connects these two nodes
+ *
+ * @param node1
+ * @param node2
+ * @return the edge object.
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ * if the nodes are not directly connected by an edge.
+ */
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ if (((MutableRootedNode)node1).getParent() == node2) {
+ return ((MutableRootedNode)node1).getEdge();
+ } else if (((MutableRootedNode)node2).getParent() == node1) {
+ return ((MutableRootedNode)node2).getEdge();
+ } else {
+ throw new NoEdgeException();
+ }
+ }
+
+ /**
+ * @param node1
+ * @param node2
+ * @return the length of the edge connecting node1 and node2.
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ * if the nodes are not directly connected by an edge.
+ */
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ if (((MutableRootedNode)node1).getParent() == node2) {
+ if (heightsKnown) {
+ return ((MutableRootedNode)node2).getHeight() - ((MutableRootedNode)node1).getHeight();
+ } else {
+ return ((MutableRootedNode)node1).getLength();
+ }
+ } else if (((MutableRootedNode)node2).getParent() == node1) {
+ if (heightsKnown) {
+ return ((MutableRootedNode)node1).getHeight() - ((MutableRootedNode)node2).getHeight();
+ } else {
+ return ((MutableRootedNode)node2).getLength();
+ }
+ } else {
+ throw new NoEdgeException();
+ }
+ }
+
+ /**
+ * @return the set of all nodes in this graph.
+ */
+ public Set<Node> getNodes() {
+ Set<Node> nodes = new HashSet<Node>(internalNodes);
+ nodes.addAll(externalNodes.values());
+ return nodes;
+ }
+
+ /**
+ * @return the set of all edges in this graph.
+ */
+ public Set<Edge> getEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getNodes()) {
+ if (node != getRootNode()) {
+ edges.add(((MutableRootedNode)node).getEdge());
+ }
+
+ }
+ return edges;
+ }
+
+ /**
+ * The set of external edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of external edges.
+ */
+ public Set<Edge> getExternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getExternalNodes()) {
+ edges.add(((MutableRootedNode)node).getEdge());
+ }
+ return edges;
+ }
+
+ /**
+ * The set of internal edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of internal edges.
+ */
+ public Set<Edge> getInternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getInternalNodes()) {
+ if (node != getRootNode()) {
+ edges.add(((MutableRootedNode)node).getEdge());
+ }
+ }
+ return edges;
+ }
+
+ /**
+ * @param degree the number of edges connected to a node
+ * @return a set containing all nodes in this graph of the given degree.
+ */
+ public Set<Node> getNodes(int degree) {
+ Set<Node> nodes = new HashSet<Node>();
+ for (Node node : getNodes()) {
+ // Account for no anncesstor of root, assumed by default in getDegree
+ final int deg = ((MutableRootedNode)node).getDegree() - ((node == rootNode) ? 1 : 0);
+ if (deg == degree) nodes.add(node);
+ }
+ return nodes;
+ }
+
+ /**
+ * Set the node heights from the current branch lengths.
+ */
+ private void calculateNodeHeights() {
+
+ if (!lengthsKnown) {
+ throw new IllegalArgumentException("Can't calculate node heights because branch lengths not known");
+ }
+
+ nodeLengthsToHeights(rootNode, 0.0);
+
+ double maxHeight = 0.0;
+ for (Node externalNode : getExternalNodes()) {
+ if (((MutableRootedNode)externalNode).getHeight() > maxHeight) {
+ maxHeight = ((MutableRootedNode)externalNode).getHeight();
+ }
+ }
+
+ for (Node node : getNodes()) {
+ ((MutableRootedNode)node).setHeight(maxHeight - ((MutableRootedNode)node).getHeight());
+ }
+
+ heightsKnown = true;
+ }
+
+ /**
+ * Set the node heights from the current node branch lengths. Actually
+ * sets distance from root so the heights then need to be reversed.
+ */
+ private void nodeLengthsToHeights(MutableRootedNode node, double height) {
+
+ double newHeight = height;
+
+ if (node.getLength() > 0.0) {
+ newHeight += node.getLength();
+ }
+
+ node.setHeight(newHeight);
+
+ for (Node child : node.getChildren()) {
+ nodeLengthsToHeights((MutableRootedNode)child, newHeight);
+ }
+ }
+
+ /**
+ * Calculate branch lengths from the current node heights.
+ */
+ protected void calculateBranchLengths() {
+
+ if (!hasLengths) {
+ throw new IllegalArgumentException("Can't calculate branch lengths because node heights not known");
+ }
+
+ nodeHeightsToLengths(rootNode, getHeight(rootNode));
+
+ lengthsKnown = true;
+ }
+
+ /**
+ * Calculate branch lengths from the current node heights.
+ */
+ private void nodeHeightsToLengths(MutableRootedNode node, double height) {
+ final double h = node.getHeight();
+ node.setLength(h >= 0 ? height - h : 1);
+
+ for (Node child : node.getChildren()) {
+ nodeHeightsToLengths((MutableRootedNode)child, node.getHeight());
+ }
+
+ }
+
+ public void setConceptuallyUnrooted(boolean intent) {
+ conceptuallyUnrooted = intent;
+ }
+
+ public boolean conceptuallyUnrooted() {
+ return conceptuallyUnrooted;
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ private AttributableHelper helper = null;
+
+ protected MutableRootedNode rootNode = null;
+ protected final Set<Node> internalNodes = new HashSet<Node>();
+ private final Map<Taxon, Node> externalNodes = new HashMap<Taxon, Node>();
+
+ private boolean heightsKnown = false;
+ private boolean lengthsKnown = false;
+
+ private boolean hasHeights = false;
+ private boolean hasLengths = false;
+
+ private boolean conceptuallyUnrooted = false;
+
+ private class MutableRootedNode extends BaseNode {
+ public MutableRootedNode(Taxon taxon) {
+ this.children = Collections.unmodifiableList(new ArrayList<Node>());
+ this.taxon = taxon;
+ }
+
+ public MutableRootedNode(List<? extends Node> children) {
+ this.children = Collections.unmodifiableList(new ArrayList<Node>(children));
+ this.taxon = null;
+ }
+
+
+ public void removeChild(Node node) {
+ List<Node> c = new ArrayList<Node>(children);
+ c.remove(node);
+ children = Collections.unmodifiableList(c);
+ }
+
+ public void addChild(MutableRootedNode node) {
+ List<Node> c = new ArrayList<Node>(children);
+ c.add(node);
+ node.setParent(this);
+ children = Collections.unmodifiableList(c);
+ }
+
+ public void replaceChildren(List<MutableRootedNode> nodes) {
+ for( MutableRootedNode n : nodes ) {
+ n.setParent(this);
+ }
+ children = Collections.unmodifiableList(new ArrayList<Node>(nodes));
+ }
+
+
+ public Node getParent() {
+ return parent;
+ }
+
+ public void setParent(Node parent) {
+ this.parent = parent;
+ }
+
+ public List<Node> getChildren() {
+ return children;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ // height above latest tip
+ public void setHeight(double height) {
+ this.height = height;
+ }
+
+ // length of branch to parent
+ public double getLength() {
+ return length >= 0 ? length : 1.0;
+ }
+
+ public void setLength(double length) {
+ this.length = length;
+ }
+
+ public int getDegree() {
+ return children.size() + 1;
+ }
+
+ /**
+ * returns the edge connecting this node to the parent node
+ * @return the edge
+ */
+ public Edge getEdge() {
+ if (edge == null) {
+ edge = new BaseEdge() {
+ public double getLength() {
+ return length;
+ }
+ };
+ }
+
+ return edge;
+ }
+
+ /**
+ * For a rooted tree, getting the adjacencies is not the most efficient
+ * operation as it makes a new set containing the children and the parent.
+ * @return the adjacaencies
+ */
+ public List<Node> getAdjacencies() {
+ List<Node> adjacencies = new ArrayList<Node>();
+ if (children != null) adjacencies.addAll(children);
+ if (parent != null) adjacencies.add(parent);
+ return adjacencies;
+ }
+
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ public void setTaxa(Taxon to) {
+ taxon = to;
+ }
+
+ private List<Node> children;
+ private Taxon taxon;
+
+ private Node parent;
+ private double height;
+ private double length;
+
+ private Edge edge = null;
+
+ }
+}
diff --git a/src/jebl/evolution/trees/NeighborJoiningTreeBuilder.java b/src/jebl/evolution/trees/NeighborJoiningTreeBuilder.java
new file mode 100644
index 0000000..434b8d5
--- /dev/null
+++ b/src/jebl/evolution/trees/NeighborJoiningTreeBuilder.java
@@ -0,0 +1,137 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Arrays;
+
+/**
+ * Constructs an unrooted tree by neighbor-joining using pairwise distances.
+ *
+ * Adapted from BEAST code.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Joseph Heled
+ *
+ * @version $Id: NeighborJoiningTreeBuilder.java 661 2007-03-20 06:13:20Z twobeers $
+ */
+public class NeighborJoiningTreeBuilder extends ClusteringTreeBuilder<Tree> {
+
+ private final SimpleTree tree;
+
+ /**
+ * construct NJ tree
+ *
+ * @param distanceMatrix distance matrix
+ */
+ public NeighborJoiningTreeBuilder(DistanceMatrix distanceMatrix) {
+ super(distanceMatrix, 3);
+
+ this.tree = new SimpleTree();
+
+ r = new double[distanceMatrix.getSize()];
+ }
+
+ //
+ // Non public part
+ //
+
+ private double[] r; // r[i] = sum of distances from node i to all other nodes
+ private double scale;
+
+ /** Find next two clusters to join. set shared best{i,j}
+ *
+ * TT: Until 2007-03-20, the comment above also claimed that this method
+ * also sets the fields <code>abi</code> and <code>abj</code>. However,
+ * this is not true and also isn't required by the contract inherited
+ * from {@link ClusteringTreeBuilder#findNextPair}.
+ *
+ * Besides, it is pretty dirty that this method's side effect is
+ * to set fields rather than return a value.
+ */
+ protected void findNextPair() {
+ for (int i = 0; i < numClusters; i++) {
+ r[i] = 0;
+ for (int j = 0; j < numClusters; j++) {
+ double dist = getDist(i, j);
+ r[i] += dist;
+ }
+ }
+
+ besti = 0;
+ bestj = 1;
+ double smax = -1.0;
+ scale = 1.0/(numClusters-2);
+ for (int i = 0; i < numClusters-1; i++) {
+ for (int j = i+1; j < numClusters; j++) {
+ double sij = (r[i] + r[j]) * scale - getDist(i, j);
+
+ if (sij > smax) {
+ smax = sij;
+ besti = i;
+ bestj = j;
+ }
+ }
+ }
+ }
+
+ protected Tree getTree() {
+ return tree;
+ }
+
+ protected Node createExternalNode(Taxon taxon) {
+ return tree.createExternalNode(taxon);
+ }
+
+ /**
+ * Creates a new internal node that will have the specified nodes as its children
+ * @param nodes Nodes whose parent is about to be created
+ * @param distances Distances of those nodes to the parent. distances.length == nodes.length
+ * must hold.
+ * @return the new node
+ */
+ protected Node createInternalNode(Node[] nodes, double[] distances) {
+ assert nodes.length == distances.length;
+
+ // create node with the specified children, but unspecified arc lengths
+ Node node = tree.createInternalNode(Arrays.asList(nodes));
+ for(int k = 0; k < nodes.length; ++k) {
+ tree.setEdgeLength(node, nodes[k], distances[k]);
+ }
+ return node;
+ }
+
+ protected void finish() {
+ // Connect up the final two clusters
+ int abi = alias[0];
+ int abj = alias[1];
+
+ double dij = getDist(0, 1);
+
+ tree.addEdge(clusters[abi], clusters[abj], dij);
+
+ super.finish();
+ }
+
+ protected double[] joinClusters() {
+ double dij = getDist(besti, bestj);
+ double li = (dij + (r[besti] - r[bestj]) * scale) * 0.5;
+ double lj = dij - li;
+
+ if (li < 0.0) li = 0.0;
+ if (lj < 0.0) lj = 0.0;
+ return new double[]{li, lj};
+ }
+
+ protected double updatedDistance(int k) {
+ final int i = besti;
+ final int j = bestj;
+
+ double d = (getDist(k, i) + getDist(k, j) - getDist(i, j)) * 0.5;
+ // Some large distances foil the method
+ return Math.max(d, 0.0);
+ }
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/RootedFromUnrooted.java b/src/jebl/evolution/trees/RootedFromUnrooted.java
new file mode 100644
index 0000000..fae2c17
--- /dev/null
+++ b/src/jebl/evolution/trees/RootedFromUnrooted.java
@@ -0,0 +1,320 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.*;
+
+/**
+ * Root an unrooted tree. This class works as a wrapper over any tree to root it. There are two
+ * constructors, one which roots the tree at any internal node, the other roots the tree between any two
+ * internal nodes. Be aware that rooting between nodes where one of them has less than 3 adjacencies may
+ * be problematic when converting back from the Newick format.
+ *
+ * @author Joseph Heled
+ * @version $Id: RootedFromUnrooted.java 627 2007-01-15 03:50:40Z pepster $
+ *
+ */
+
+public class RootedFromUnrooted implements RootedTree {
+ /**
+ * The unrooted tree
+ */
+ private Tree source;
+
+ /**
+ * Root of rooted tree. Either an existing internal node or a new "synthetic" node.
+ */
+ private Node root;
+ /**
+ * Maps each nodes to its parent.
+ */
+ private Map<Node, Node> parents;
+
+ /**
+ * Children of the synthetic root (when rooted between nodes)
+ */
+ private Node topLeft, topRight;
+ /**
+ * branch lengths from synthetic root to its children (when rooted between nodes)
+ */
+ private double rootToLeft, rootToRight;
+ private boolean intentUnrooted;
+
+ /**
+ * Set <arg>parent</arg> as parent of <arg>node</arg>, and recursivly set parents for node subtree
+ * (whose root is parent)
+ * @param node
+ * @param parent
+ */
+ private void setParent(Node node, Node parent) {
+ parents.put(node, parent);
+ for( Node adj : source.getAdjacencies(node) ) {
+ if( adj != parent && ! (node == topLeft && adj == topRight) && !(node == topRight && adj == topLeft) ) {
+ setParent(adj, node);
+ }
+ }
+ }
+
+ /**
+ * Root tree at some internal node.
+ *
+ * @param source tree to root
+ * @param root internal node to root at
+ * @param intentUnrooted
+ */
+ public RootedFromUnrooted(Tree source, Node root, boolean intentUnrooted) {
+ this.source = source;
+ this.root = root;
+ this.intentUnrooted = intentUnrooted;
+ topLeft = topRight = null;
+ rootToLeft = rootToRight = 0.0;
+ parents = new HashMap<Node, Node>();
+ for( Node adj : source.getAdjacencies(root) ) {
+ setParent(adj, root);
+ }
+ }
+
+ /**
+ * Root source by creating a new internal node whose children are (the adjacent) left and right.
+ * @param source
+ * @param left
+ * @param right
+ * @param fromLeft branch from new root to left node.
+ */
+ public RootedFromUnrooted(Tree source, Node left, Node right, double fromLeft) {
+ this.source = source;
+ intentUnrooted = false;
+ topLeft = left;
+ topRight = right;
+ rootToLeft = fromLeft;
+ try {
+ rootToRight = source.getEdgeLength(left, right) - rootToLeft;
+ } catch (NoEdgeException e) {
+ // bug
+ }
+ parents = new HashMap<Node, Node>();
+
+ // This is just a handle used to refer to the root so create the simplest possible implementation...
+ root = new BaseNode() { public int getDegree() { return 0; } };
+
+ parents.put(root, null);
+ setParent(left, root);
+ setParent(right, root);
+ }
+
+ public List<Node> getChildren(Node node) {
+ ArrayList<Node> s = new ArrayList<Node>(getAdjacencies(node));
+ if( node != root ) {
+ s.remove(getParent(node));
+ }
+ return s;
+ }
+
+ public boolean hasHeights() {
+ return false;
+ }
+
+ private double findNodeHeightFromTips(Node node) {
+ if( isExternal(node) ) return 0.0;
+
+ double h = 0.0;
+ for( Node n : getChildren(node) ) {
+ h = Math.max(h, getLength(n) + findNodeHeightFromTips(n));
+ }
+ return h;
+ }
+
+ public double getHeight(Node node) {
+ double hr = findNodeHeightFromTips(root);
+ if( node == root ) {
+ return hr;
+ }
+
+ double toRoot = 0.0;
+ while( node != root ) {
+ toRoot += getLength(node);
+ node = getParent(node);
+ }
+ return hr - toRoot;
+ }
+
+ public boolean hasLengths() {
+ return true;
+ }
+
+ public double getLength(Node node) {
+ if( node == root ) return 0.0;
+ if( node == topLeft ) return rootToLeft;
+ if( node == topRight ) return rootToRight;
+ double l = 0.0;
+ try {
+ l = source.getEdgeLength(node, getParent(node));
+ } catch (NoEdgeException e) {
+ // bug, should not happen
+ }
+ return l;
+ }
+
+ public Node getParent(Node node) {
+ return parents.get(node);
+ }
+
+ public Node getRootNode() {
+ return root;
+ }
+
+ public boolean conceptuallyUnrooted() {
+ return intentUnrooted;
+ }
+
+ public Set<Node> getExternalNodes() {
+ return source.getExternalNodes();
+ }
+
+ public Set<Node> getInternalNodes() {
+ HashSet<Node> s = new HashSet<Node>(source.getInternalNodes());
+ s.add(root);
+ return s;
+ }
+
+ public Set<Taxon> getTaxa() {
+ return source.getTaxa();
+ }
+
+ public Taxon getTaxon(Node node) {
+ return source.getTaxon(node);
+ }
+
+ public boolean isExternal(Node node) {
+ return node != root && source.isExternal(node);
+ }
+
+ public Node getNode(Taxon taxon) {
+ return source.getNode(taxon);
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ source.renameTaxa(from, to);
+ }
+
+ /**
+ * Returns a list of edges connected to this node
+ *
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Edge> getEdges(Node node) {
+ return source.getEdges(node);
+ }
+
+ public Node[] getNodes(Edge edge) {
+ return source.getNodes(edge);
+ }
+
+ public List<Node> getAdjacencies(Node node) {
+ // special case when syntetic root
+ if( topLeft != null ) {
+ if( node == root ) {
+ Node[] d = {topLeft, topRight};
+ return Arrays.asList(d);
+ }
+ if( node == topLeft || node == topRight ) {
+ List<Node> s = new ArrayList<Node>(source.getAdjacencies(node));
+ s.remove(node == topLeft ? topRight : topLeft);
+ s.add(root);
+ return s;
+ }
+ }
+ return source.getAdjacencies(node);
+ }
+
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ // special case when syntetic root
+ if( topLeft != null ) {
+ if( node2 == root ) {
+ Node tmp = node1;
+ node1 = node2;
+ node2 = tmp;
+ }
+ if( node1 == root ) {
+ if( ! (node2 == topLeft || node2 == topRight) ) {
+ throw new NoEdgeException();
+ }
+ return node2 == topLeft ? rootToLeft : rootToRight;
+ }
+ }
+ return source.getEdgeLength(node1, node2);
+ }
+
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ return source.getEdge(node1, node2);
+ }
+
+ public Set<Node> getNodes() {
+ Set<Node> nodes = new HashSet<Node>(getInternalNodes());
+ nodes.addAll(getExternalNodes());
+ if( topLeft != null ) {
+ nodes.add(root);
+ }
+ return nodes;
+ }
+
+ /**
+ * @return the set of all edges in this graph.
+ */
+ public Set<Edge> getEdges() {
+ return source.getEdges();
+ }
+
+ /**
+ * The set of external edges.
+ * @return the set of external edges.
+ */
+ public Set<Edge> getExternalEdges() {
+ return source.getExternalEdges();
+ }
+
+ /**
+ * The set of internal edges.
+ * @return the set of internal edges.
+ */
+ public Set<Edge> getInternalEdges() {
+ return source.getInternalEdges();
+ }
+
+ public Set<Node> getNodes(int degree) {
+ Set<Node> nodes = source.getNodes(degree);
+ if( degree == 2 ) {
+ nodes.add(root);
+ }
+ return nodes;
+ }
+
+ public boolean isRoot(Node node) {
+ return node == root;
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ source.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ return source.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ source.removeAttribute(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ return source.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ return source.getAttributeMap();
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/RootedTree.java b/src/jebl/evolution/trees/RootedTree.java
new file mode 100644
index 0000000..a45a1a2
--- /dev/null
+++ b/src/jebl/evolution/trees/RootedTree.java
@@ -0,0 +1,95 @@
+/*
+ * RootedTree.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+
+import java.util.List;
+
+/**
+ * A tree with a root (node with maximum height). This interface
+ * provides the concept of a direction of time that flows from the
+ * root to the tips. Each node in the tree has a node height that is
+ * less than its parent's height and greater than it children's heights.
+ *
+ * @author rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: RootedTree.java 529 2006-11-14 01:13:52Z matt_kearse $
+ */
+public interface RootedTree extends Tree {
+
+ /**
+ * @param node the node whose children are being requested.
+ * @return the list of nodes that are the children of the given node.
+ * The set may be empty for a terminal node (a tip).
+ */
+ List<Node> getChildren(Node node);
+
+ /**
+ * @return Whether this tree has node heights available
+ */
+ boolean hasHeights();
+
+ /**
+ * @param node the node whose height is being requested.
+ * @return the height of the given node. The height will be
+ * less than the parent's height and greater than it children's heights.
+ */
+ double getHeight(Node node);
+
+ /**
+ * @return Whether this tree has branch lengths available
+ */
+ boolean hasLengths();
+
+ /**
+ * @param node the node whose branch length (to its parent) is being requested.
+ * @return the length of the branch to the parent node (0.0 if the node is the root).
+ */
+ double getLength(Node node);
+
+ /**
+ * @param node the node whose parent is requested
+ * @return the parent node of the given node, or null
+ * if the node is the root node.
+ */
+ Node getParent(Node node);
+
+ /**
+ * The root of the tree has the largest node height of
+ * all nodes in the tree.
+ * @return the root of the tree.
+ */
+ Node getRootNode();
+
+ /**
+ * Due to current implementation limitations, trees store "branch" information in nodes. So, internally rooted trees
+ * are genetrated when un-rooted would be more natural.
+ *
+ * This should be removed. If this is a rooted tree then it is rooted. This can really
+ * only confuse things. Trees are unrooted, RootedTrees are rooted. This is not an implementation
+ * limitation. It may be that a RootedTree has an arbitrary root but it is still rooted. With a rooted
+ * tree, it is convenient to store branch information at the node (i.e., for the branch above the node)
+ * because there is no "branch" object. Andrew.
+ *
+ * This function will probably become deprecated once the "development"
+ * tree viewer becomes in sync with the main tree viewer branch and some
+ * method of handling this concept has been introduced. Until then, this method remains.
+ *
+ * @return true if tree(s) are to be viewed as unrooted
+ */
+ boolean conceptuallyUnrooted();
+
+ /**
+ * @param node the node
+ * @return true if the node is the root of this tree.
+ */
+ boolean isRoot(Node node);
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/RootedTreeUtils.java b/src/jebl/evolution/trees/RootedTreeUtils.java
new file mode 100644
index 0000000..3e35a57
--- /dev/null
+++ b/src/jebl/evolution/trees/RootedTreeUtils.java
@@ -0,0 +1,308 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.taxa.MissingTaxonException;
+
+import java.util.*;
+
+/**
+ * Static utility functions for rooted trees.
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: RootedTreeUtils.java 433 2006-08-27 19:34:13Z rambaut $
+ */
+
+public class RootedTreeUtils {
+
+ /**
+ * Return the number of leaves under this node.
+ * @param tree
+ * @param node
+ * @return the number of leaves under this node.
+ */
+ public static final int getTipCount(RootedTree tree, Node node) {
+ int tipCount = 0;
+ for (Node child : tree.getChildren(node)) {
+ tipCount += getTipCount(tree, child);
+ }
+
+ // is external
+ if (tipCount == 0) return 1;
+
+ return tipCount;
+ }
+
+ public static double getMinTipHeight(RootedTree tree, Node node) {
+ if (tree.isExternal(node)) {
+ return tree.getHeight(node);
+ }
+
+ double minTipHeight = Double.MAX_VALUE;
+ for (Node child : tree.getChildren(node)) {
+ double h = getMinTipHeight(tree, child);
+ if (h < minTipHeight) {
+ minTipHeight = h;
+ }
+ }
+
+ return minTipHeight;
+ }
+
+ public static double getMaxTipHeight(RootedTree tree, Node node) {
+ if (tree.isExternal(node)) {
+ return tree.getHeight(node);
+ }
+
+ double maxTipHeight = -Double.MAX_VALUE;
+ for (Node child : tree.getChildren(node)) {
+ double h = getMaxTipHeight(tree, child);
+ if (h > maxTipHeight) {
+ maxTipHeight = h;
+ }
+ }
+
+ return maxTipHeight;
+ }
+
+ /**
+ * @return true only if all tips have height 0.0
+ */
+ public static boolean isUltrametric(RootedTree tree, double tolerance) {
+ for (Node node : tree.getExternalNodes()) {
+ if (Math.abs(tree.getHeight(node)) > tolerance) return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return true only if internal nodes have 2 children
+ */
+ public static boolean isBinary(RootedTree tree) {
+ for (Node node : tree.getInternalNodes()) {
+ if (tree.getChildren(node).size() > 2) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets a set of external nodes that correspond to the given taxa.
+ */
+ public static Set<Node> getTipsForTaxa(RootedTree tree, Collection<Taxon> taxa) throws MissingTaxonException {
+
+ Set<Node> tipNodes = new HashSet<Node>();
+
+ for (Taxon taxon : taxa) {
+
+ Node node = tree.getNode(taxon);
+
+ if (node == null) {
+ throw new MissingTaxonException(taxon);
+ }
+
+ tipNodes.add(node);
+ }
+
+ return tipNodes;
+ }
+
+ /**
+ * Gets a set of tip nodes descended from the given node.
+ */
+ public static Set<Node> getDescendantTips(RootedTree tree, Node node) {
+
+ Set<Node> tipNodes = new HashSet<Node>();
+ getDescendantTips(tree, node, tipNodes);
+ return tipNodes;
+ }
+
+ /**
+ * Private recursive function used by getDescendantTips.
+ */
+ private static void getDescendantTips(RootedTree tree, Node node, Set<Node> tipNodes) {
+
+ for (Node child : tree.getChildren(node)) {
+ if (tree.isExternal(child)) {
+ tipNodes.add(child);
+ } else {
+ getDescendantTips(tree, child, tipNodes);
+ }
+ }
+ }
+
+ /**
+ * Gets the most recent common ancestor (MRCA) node of a set of tip nodes.
+ * @param tree the Tree
+ * @param tipNodes a set of tip nodes
+ * @return the Node of the MRCA
+ */
+ public static Node getCommonAncestorNode(RootedTree tree, Set<Node> tipNodes) {
+
+ if (tipNodes.size() == 0) {
+ throw new IllegalArgumentException("No leaf nodes selected");
+ }
+
+ if (tipNodes.size() == 1) return tipNodes.iterator().next();
+
+ Node[] mrca = new Node[] { null };
+ getCommonAncestorNode(tree, tree.getRootNode(), tipNodes, mrca);
+
+ return mrca[0];
+ }
+
+ /**
+ * Private recursive function used by getCommonAncestorNode.
+ */
+ private static int getCommonAncestorNode(RootedTree tree, Node node,
+ Set<Node> tipNodes,
+ Node[] mrca) {
+
+
+ int matches = 0;
+
+ for (Node child : tree.getChildren(node)) {
+ if (tree.isExternal(child)) {
+ if (tipNodes.contains(child)) {
+ matches ++;
+ }
+ } else {
+
+ matches += getCommonAncestorNode(tree, child, tipNodes, mrca);
+
+ if (mrca[0] != null) {
+ return matches;
+ }
+ }
+ }
+
+ // If we haven't already found the MRCA, test this node
+ if (matches == tipNodes.size()) {
+ mrca[0] = node;
+ }
+
+ return matches;
+ }
+
+ /**
+ * Performs the a monophyly test on a set of tip nodes. The nodes are monophyletic
+ * if there is a node in the tree which subtends all the tips in the set (and
+ * only those tips).
+ * @param tree a tree object to perform test on
+ * @param tipNodes a set containing the tip node.
+ * @return boolean is monophyletic?
+ */
+ public static boolean isMonophyletic(RootedTree tree, Set<Node> tipNodes) {
+
+ if (tipNodes.size() == 0) {
+ throw new IllegalArgumentException("No tip nodes selected");
+ }
+
+ if (tipNodes.size() == 1) {
+ // A single selected leaf is always monophyletic
+ return true;
+ }
+
+ if (tipNodes.size() == tree.getExternalNodes().size()) {
+ // All leaf nodes are selected
+ return true;
+ }
+
+ int[] matchCount = new int[] { 0 };
+ int[] tipCount = new int[] { 0 };
+
+ Boolean result = isMonophyletic(tree, tree.getRootNode(), tipNodes, matchCount, tipCount);
+
+ if (result != null) return result;
+
+ return false;
+ }
+
+ /**
+ * Private recursive function used by isMonophyletic.
+ */
+ private static Boolean isMonophyletic(RootedTree tree, Node node,
+ Set<Node> tipNodes,
+ int[] matchCount, int[] tipCount) {
+
+ int mc = 0;
+ int tc = 0;
+
+ for (Node child : tree.getChildren(node)) {
+ if (tree.isExternal(child)) {
+ if (tipNodes.contains(child)) {
+ mc ++;
+ }
+ tc ++;
+ } else {
+
+ Boolean result = isMonophyletic(tree, child, tipNodes, matchCount, tipCount);
+
+ if (result != null) {
+ return result;
+ }
+
+ mc += matchCount[0];
+ tc += tipCount[0];
+ }
+ }
+
+
+ matchCount[0] = mc;
+ tipCount[0] = tc;
+
+ // If we haven't already found the MRCA, test this node
+ if (mc == tc && tc == tipNodes.size()) {
+ // monophyletic
+ return Boolean.TRUE;
+ }
+
+ if (mc != 0 && mc != tc) {
+ // not monophyletic
+ return Boolean.FALSE;
+ }
+
+ // no result yet
+ return null;
+ }
+
+ /**
+ * Recursive function for constructing a newick tree representation in the given buffer.
+ */
+ public static String uniqueNewick(RootedTree tree, Node node) {
+ if (tree.isExternal(node)) {
+ return tree.getTaxon(node).getName();
+ } else {
+ StringBuffer buffer = new StringBuffer("(");
+
+ List<String> subtrees = new ArrayList<String>();
+
+ for (Node child : tree.getChildren(node)) {
+ subtrees.add(uniqueNewick(tree, child));
+ }
+ Collections.sort(subtrees);
+
+ for (int i = 0; i < subtrees.size(); i++) {
+ buffer.append(subtrees.get(i));
+ if (i < subtrees.size() - 1) {
+ buffer.append(",");
+ }
+ }
+ buffer.append(")");
+
+ return buffer.toString();
+ }
+ }
+
+ /**
+ * Compares 2 trees and returns true if they have the same topology.
+ */
+ public static boolean equal(RootedTree tree1, RootedTree tree2) {
+
+ return uniqueNewick(tree1, tree1.getRootNode()).equals(uniqueNewick(tree2, tree2.getRootNode()));
+ }
+
+
+}
+
diff --git a/src/jebl/evolution/trees/SimpleRootedTree.java b/src/jebl/evolution/trees/SimpleRootedTree.java
new file mode 100644
index 0000000..4a31554
--- /dev/null
+++ b/src/jebl/evolution/trees/SimpleRootedTree.java
@@ -0,0 +1,741 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.AttributableHelper;
+
+import java.util.*;
+
+/**
+ * A simple, and initially immutable rooted tree implementation. All returned collections
+ * are defensively copied. The implementation of Node is private. A number of methods are
+ * provided that can be used to construct a tree (createExternalNode & createInternalNode).
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: SimpleRootedTree.java 708 2007-05-09 23:36:45Z stevensh $
+ */
+final public class SimpleRootedTree implements RootedTree {
+
+ public SimpleRootedTree() {
+ }
+
+ /**
+ * Make a copy of the given rooted tree
+ * @param tree a rooted tree
+ */
+ public SimpleRootedTree(RootedTree tree) {
+ createNodes(tree, tree.getRootNode());
+ }
+
+ /**
+ * Make a copy of the given rooted tree
+ * @param tree a rooted tree
+ * @param nodeMapping store {source tree node -> new tree node} mapping in here if non-null
+ */
+ public SimpleRootedTree(RootedTree tree, Map<Node, Node> nodeMapping) {
+ createNodes(tree, tree.getRootNode(), nodeMapping);
+ setConceptuallyUnrooted(tree.conceptuallyUnrooted());
+ }
+
+ /**
+ * Make a copy of the given unrooted tree
+ * @param tree an unrooted tree
+ * @param ingroupNode the node on one side of the root
+ * @param outgroupNode the node on the other side of the root
+ * @param ingroupBranchLength the branch length from the root to the ingroup node
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ */
+ public SimpleRootedTree(Tree tree, Node ingroupNode, Node outgroupNode, double ingroupBranchLength) throws NoEdgeException {
+ List<Node> children = new ArrayList<Node>();
+
+ Node node1 = createNodes(tree, outgroupNode, ingroupNode);
+ setLength(node1, ingroupBranchLength);
+ children.add(node1);
+
+ Node node2 = createNodes(tree, ingroupNode, outgroupNode);
+ setLength(node1, Math.max(tree.getEdgeLength(ingroupNode, outgroupNode) - ingroupBranchLength, 0.0));
+ children.add(node2);
+
+ createInternalNode(children);
+ }
+
+ /**
+ * Clones the entire tree structure from the given RootedTree.
+ * @param tree
+ * @param node
+ * @return created node
+ */
+ public Node createNodes(RootedTree tree, Node node) {
+ return createNodes(tree, node, (Map<Node, Node>)null);
+ }
+
+ /**
+ * Clones the entire tree structure from the given RootedTree.
+ * @param tree
+ * @param node
+ * @param nodeMapping may be null
+ * @return
+ */
+ private Node createNodes(RootedTree tree, Node node, Map<Node, Node> nodeMapping) {
+
+ Node newNode;
+ if (tree.isExternal(node)) {
+ newNode = createExternalNode(tree.getTaxon(node));
+
+ } else {
+ List<Node> children = new ArrayList<Node>();
+ for (Node child : tree.getChildren(node)) {
+ children.add(createNodes(tree, child, nodeMapping));
+ }
+ newNode = createInternalNode(children);
+ }
+
+ if( nodeMapping != null ) nodeMapping.put(node, newNode);
+
+// final Map<String, Object> map = node.getAttributeMap();
+// if( ! map.isEmpty() ) {
+ for( Map.Entry<String, Object> e : node.getAttributeMap().entrySet() ) {
+ newNode.setAttribute(e.getKey(), e.getValue());
+ }
+ // }
+ setHeight(newNode, tree.getHeight(node));
+
+ return newNode;
+ }
+
+ /**
+ * Clones the entire tree structure from the given (unrooted) Tree.
+ * @param tree the unrooted tree
+ * @param parent the parent node
+ * @param child the child node
+ */
+ public Node createNodes(Tree tree, Node parent, Node child) throws NoEdgeException {
+
+ Node newNode = null;
+ if (tree.isExternal(child)) {
+ newNode = createExternalNode(tree.getTaxon(child));
+ } else {
+ List<Node> adjacencies = tree.getAdjacencies(child);
+ List<Node> children = new ArrayList<Node>();
+
+ for (Node child2 : adjacencies) {
+ if (child2 != parent) {
+ children.add(createNodes(tree, child, child2));
+ }
+ }
+ newNode = createInternalNode(children);
+ }
+
+ setLength(newNode, tree.getEdgeLength(parent, child));
+
+ return newNode;
+ }
+
+ /**
+ * Creates a new external node with the given taxon. See createInternalNode
+ * for a description of how to use these methods.
+ * @param taxon the taxon associated with this node
+ * @return the created node reference
+ */
+ public Node createExternalNode(Taxon taxon) {
+ if( getTaxa().contains(taxon) ) {
+ throw new IllegalArgumentException("duplicate taxon");
+ }
+
+ SimpleRootedNode node = new SimpleRootedNode(taxon);
+ externalNodes.put(taxon, node);
+ return node;
+ }
+
+ /**
+ * Once a SimpleRootedTree has been created, the node stucture can be created by
+ * calling createExternalNode and createInternalNode. First of all createExternalNode
+ * is called giving Taxon objects for the external nodes. Then these are put into
+ * sets and passed to createInternalNode to create a parent of these nodes. The
+ * last node created using createInternalNode is automatically the root so when
+ * all the nodes are created, the tree is complete.
+ *
+ * @param children the child nodes of this nodes
+ * @return the created node reference
+ */
+ public SimpleRootedNode createInternalNode(List<? extends Node> children) {
+ SimpleRootedNode node = new SimpleRootedNode(children);
+
+ for (Node child : children) {
+ ((SimpleRootedNode)child).setParent(node);
+ }
+
+ internalNodes.add(node);
+
+ rootNode = node;
+ return node;
+ }
+
+ public void swapNodes(Node n, int i0, int i1) {
+ ((SimpleRootedNode)n).swapChildren(i0, i1);
+ }
+
+ /**
+ * @param node the node whose height is being set
+ * @param height the height
+ */
+ public void setHeight(Node node, double height) {
+ lengthsKnown = false;
+ heightsKnown = true;
+
+ // If a single height of a single node is set then
+ // assume that all nodes have heights and by extension,
+ // branch lengths as well as these will be calculated
+ // from the heights
+ hasLengths = true;
+ hasHeights = true;
+
+ ((SimpleRootedNode)node).setHeight(height);
+ }
+
+ /**
+ * @param node the node whose branch length (to its parent) is being set
+ * @param length the length
+ */
+ public void setLength(Node node, double length) {
+ heightsKnown = false;
+ lengthsKnown = true;
+
+ // If a single length of a single branch is set then
+ // assume that all branch have lengths and by extension,
+ // node heights as well as these will be calculated
+ // from the lengths
+ hasLengths = true;
+ hasHeights = true;
+
+ ((SimpleRootedNode)node).setLength(length);
+ }
+
+ /**
+ * @param node the node whose children are being requested.
+ * @return the list of nodes that are the children of the given node.
+ * The list may be empty for a terminal node (a tip).
+ */
+ public List<Node> getChildren(Node node) {
+ return new ArrayList<Node>(((SimpleRootedNode)node).getChildren());
+ }
+
+ /**
+ * @return Whether this tree has node heights available
+ */
+ public boolean hasHeights() {
+ return hasHeights;
+ }
+
+ /**
+ * @param node the node whose height is being requested.
+ * @return the height of the given node. The height will be
+ * less than the parent's height and greater than it children's heights.
+ */
+ public double getHeight(Node node) {
+ if (!hasHeights) throw new IllegalArgumentException("This tree has no node heights");
+ if (!heightsKnown) calculateNodeHeights();
+ return ((SimpleRootedNode)node).getHeight();
+ }
+
+ /**
+ * @return Whether this tree has branch lengths available
+ */
+ public boolean hasLengths() {
+ return hasLengths;
+ }
+
+ /**
+ * @param node the node whose branch length (to its parent) is being requested.
+ * @return the length of the branch to the parent node (0.0 if the node is the root).
+ */
+ public double getLength(Node node) {
+ if (!hasLengths) throw new IllegalArgumentException("This tree has no branch lengths");
+ if (!lengthsKnown) calculateBranchLengths();
+ return ((SimpleRootedNode)node).getLength();
+ }
+
+ /**
+ * @param node the node whose parent is requested
+ * @return the parent node of the given node, or null
+ * if the node is the root node.
+ */
+ public Node getParent(Node node) {
+ return ((SimpleRootedNode)node).getParent();
+ }
+
+ /**
+ * The root of the tree has the largest node height of
+ * all nodes in the tree.
+ *
+ * @return the root of the tree.
+ */
+ public Node getRootNode() {
+ return rootNode;
+ }
+
+
+ /**
+ * @return a set of all nodes that have degree 1.
+ * These nodes are often refered to as 'tips'.
+ */
+ public Set<Node> getExternalNodes() {
+ return new HashSet<Node>(externalNodes.values());
+ }
+
+ /**
+ * @return a set of all nodes that have degree 2 or more.
+ * These nodes are often refered to as internal nodes.
+ */
+ public Set<Node> getInternalNodes() {
+ return new HashSet<Node>(internalNodes);
+ }
+
+ /**
+ * @return the set of taxa associated with the external
+ * nodes of this tree. The size of this set should be the
+ * same as the size of the external nodes set.
+ */
+ public Set<Taxon> getTaxa() {
+ return new HashSet<Taxon>(externalNodes.keySet());
+ }
+
+ /**
+ * @param node the node whose associated taxon is being requested.
+ * @return the taxon object associated with the given node, or null
+ * if the node is an internal node.
+ */
+ public Taxon getTaxon(Node node) {
+ return ((SimpleRootedNode)node).getTaxon();
+ }
+
+ /**
+ * @param node the node
+ * @return true if the node is of degree 1.
+ */
+ public boolean isExternal(Node node) {
+ return ((SimpleRootedNode)node).getChildren().size() == 0;
+ }
+
+ /**
+ * @param taxon the taxon
+ * @return the external node associated with the given taxon, or null
+ * if the taxon is not a member of the taxa set associated with this tree.
+ */
+ public Node getNode(Taxon taxon) {
+ return externalNodes.get(taxon);
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ SimpleRootedNode node = (SimpleRootedNode)externalNodes.get(from);
+ node.setTaxa(to);
+ }
+
+ /**
+ * Returns a list of edges connected to this node
+ *
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Edge> getEdges(Node node) {
+ List<Edge> edges = new ArrayList<Edge>();
+ for (Node adjNode : getAdjacencies(node)) {
+ edges.add(((SimpleRootedNode)adjNode).getEdge());
+
+ }
+ return edges;
+ }
+
+ /**
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Node> getAdjacencies(Node node) {
+ return ((SimpleRootedNode)node).getAdjacencies();
+ }
+
+ /**
+ * Returns the Edge that connects these two nodes
+ *
+ * @param node1
+ * @param node2
+ * @return the edge object.
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ * if the nodes are not directly connected by an edge.
+ */
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ if (((SimpleRootedNode)node1).getParent() == node2) {
+ return ((SimpleRootedNode)node1).getEdge();
+ } else if (((SimpleRootedNode)node2).getParent() == node1) {
+ return ((SimpleRootedNode)node2).getEdge();
+ } else {
+ throw new NoEdgeException();
+ }
+ }
+
+ /**
+ * @param node1
+ * @param node2
+ * @return the length of the edge connecting node1 and node2.
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ * if the nodes are not directly connected by an edge.
+ */
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ if (((SimpleRootedNode)node1).getParent() == node2) {
+ if (heightsKnown) {
+ return ((SimpleRootedNode)node2).getHeight() - ((SimpleRootedNode)node1).getHeight();
+ } else {
+ return ((SimpleRootedNode)node1).getLength();
+ }
+ } else if (((SimpleRootedNode)node2).getParent() == node1) {
+ if (heightsKnown) {
+ return ((SimpleRootedNode)node1).getHeight() - ((SimpleRootedNode)node2).getHeight();
+ } else {
+ return ((SimpleRootedNode)node2).getLength();
+ }
+ } else {
+ throw new NoEdgeException();
+ }
+ }
+
+ /**
+ * Returns an array of 2 nodes which are the nodes at either end of the edge.
+ *
+ * @param edge
+ * @return an array of 2 edges
+ */
+ public Node[] getNodes(Edge edge) {
+ for (Node node : getNodes()) {
+ if (((SimpleRootedNode)node).getEdge() == edge) {
+ return new Node[] { node, ((SimpleRootedNode)node).getParent() };
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the set of all nodes in this graph.
+ */
+ public Set<Node> getNodes() {
+ Set<Node> nodes = new HashSet<Node>(internalNodes);
+ nodes.addAll(externalNodes.values());
+ return nodes;
+ }
+
+ /**
+ * @return the set of all edges in this graph.
+ */
+ public Set<Edge> getEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getNodes()) {
+ if (node != getRootNode()) {
+ edges.add(((SimpleRootedNode)node).getEdge());
+ }
+
+ }
+ return edges;
+ }
+
+ /**
+ * The set of external edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of external edges.
+ */
+ public Set<Edge> getExternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getExternalNodes()) {
+ edges.add(((SimpleRootedNode)node).getEdge());
+ }
+ return edges;
+ }
+
+ /**
+ * The set of internal edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of internal edges.
+ */
+ public Set<Edge> getInternalEdges() {
+ Set<Edge> edges = new HashSet<Edge>();
+ for (Node node : getInternalNodes()) {
+ if (node != getRootNode()) {
+ edges.add(((SimpleRootedNode)node).getEdge());
+ }
+ }
+ return edges;
+ }
+
+ /**
+ * @param degree the number of edges connected to a node
+ * @return a set containing all nodes in this graph of the given degree.
+ */
+ public Set<Node> getNodes(int degree) {
+ Set<Node> nodes = new HashSet<Node>();
+ for (Node node : getNodes()) {
+ // Account for no anncesstor of root, assumed by default in getDegree
+ final int deg = ((SimpleRootedNode)node).getDegree() - ((node == rootNode) ? 1 : 0);
+ if (deg == degree) nodes.add(node);
+ }
+ return nodes;
+ }
+
+ /**
+ * Set the node heights from the current branch lengths.
+ */
+ private void calculateNodeHeights() {
+
+ if (!lengthsKnown) {
+ throw new IllegalArgumentException("Can't calculate node heights because branch lengths not known");
+ }
+
+ nodeLengthsToHeights(rootNode, 0.0);
+
+ double maxHeight = 0.0;
+ for (Node externalNode : getExternalNodes()) {
+ if (((SimpleRootedNode)externalNode).getHeight() > maxHeight) {
+ maxHeight = ((SimpleRootedNode)externalNode).getHeight();
+ }
+ }
+
+ for (Node node : getNodes()) {
+ ((SimpleRootedNode)node).setHeight(maxHeight - ((SimpleRootedNode)node).getHeight());
+ }
+
+ heightsKnown = true;
+ }
+
+ /**
+ * Set the node heights from the current node branch lengths. Actually
+ * sets distance from root so the heights then need to be reversed.
+ */
+ private void nodeLengthsToHeights(SimpleRootedNode node, double height) {
+
+ double newHeight = height;
+
+ if (node.getLength() > 0.0) {
+ newHeight += node.getLength();
+ }
+
+ node.setHeight(newHeight);
+
+ for (Node child : node.getChildren()) {
+ nodeLengthsToHeights((SimpleRootedNode)child, newHeight);
+ }
+ }
+
+ /**
+ * Calculate branch lengths from the current node heights.
+ */
+ protected void calculateBranchLengths() {
+
+ if (!hasLengths) {
+ throw new IllegalArgumentException("Can't calculate branch lengths because node heights not known");
+ }
+
+ nodeHeightsToLengths(rootNode, getHeight(rootNode));
+
+ lengthsKnown = true;
+ }
+
+ /**
+ * Calculate branch lengths from the current node heights.
+ */
+ private void nodeHeightsToLengths(SimpleRootedNode node, double height) {
+ final double h = node.getHeight();
+ node.setLength(h >= 0 ? height - h : 1);
+
+ for (Node child : node.getChildren()) {
+ nodeHeightsToLengths((SimpleRootedNode)child, node.getHeight());
+ }
+
+ }
+
+ public void setConceptuallyUnrooted(boolean intent) {
+ conceptuallyUnrooted = intent;
+ }
+
+ public boolean conceptuallyUnrooted() {
+ return conceptuallyUnrooted;
+ }
+
+ public boolean isRoot(Node node) {
+ return node == rootNode;
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ private AttributableHelper helper = null;
+
+ protected SimpleRootedNode rootNode = null;
+ protected final Set<Node> internalNodes = new HashSet<Node>();
+ private final Map<Taxon, Node> externalNodes = new HashMap<Taxon, Node>();
+
+ private boolean heightsKnown = false;
+ private boolean lengthsKnown = false;
+
+ private boolean hasHeights = false;
+ private boolean hasLengths = false;
+
+ private boolean conceptuallyUnrooted = false;
+
+ private class SimpleRootedNode extends BaseNode {
+ public SimpleRootedNode(Taxon taxon) {
+ this.children = Collections.unmodifiableList(new ArrayList<Node>());
+ this.taxon = taxon;
+ }
+
+ public SimpleRootedNode(List<? extends Node> children) {
+ this.children = Collections.unmodifiableList(new ArrayList<Node>(children));
+ this.taxon = null;
+ }
+
+ public void removeChild(Node node) {
+ List<Node> c = new ArrayList<Node>(children);
+ c.remove(node);
+ children = Collections.unmodifiableList(c);
+ }
+
+ public void addChild(SimpleRootedNode node) {
+ List<Node> c = new ArrayList<Node>(children);
+ c.add(node);
+ node.setParent(this);
+ children = Collections.unmodifiableList(c);
+ }
+
+ public void replaceChildren(List<SimpleRootedNode> nodes) {
+ for( SimpleRootedNode n : nodes ) {
+ n.setParent(this);
+ }
+ children = Collections.unmodifiableList(new ArrayList<Node>(nodes));
+ }
+
+ void swapChildren(int i0, int i1) {
+ ArrayList<Node> nc = new ArrayList<Node>(children);
+ //there was a user reported crash where i0 was > size of the array of children nodes
+ if(i0 >= nc.size() || i1 >= nc.size()){
+ assert false : "SimpleRootedNode.swapChildren() was called with invalid parameters!";
+ return;
+ }
+ final Node ni0 = nc.get(i0);
+ nc.set(i0, nc.get(i1));
+ nc.set(i1, ni0);
+ children = nc;
+ }
+
+ public Node getParent() {
+ return parent;
+ }
+
+ public void setParent(Node parent) {
+ this.parent = parent;
+ }
+
+ public List<Node> getChildren() {
+ return children;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ // height above latest tip
+ public void setHeight(double height) {
+ this.height = height;
+ }
+
+ // length of branch to parent
+ public double getLength() {
+ return length >= 0 ? length : 1.0;
+ }
+
+ public void setLength(double length) {
+ this.length = length;
+ }
+
+ public int getDegree() {
+ return children.size() + 1;
+ }
+
+ public void setTaxa(Taxon to) {
+ taxon = to;
+ }
+
+ /**
+ * returns the edge connecting this node to the parent node
+ * @return the edge
+ */
+ public Edge getEdge() {
+ if (edge == null) {
+ edge = new BaseEdge() {
+ public double getLength() {
+ return length;
+ }
+ };
+ }
+
+ return edge;
+ }
+
+ /**
+ * For a rooted tree, getting the adjacencies is not the most efficient
+ * operation as it makes a new set containing the children and the parent.
+ * @return the adjacaencies
+ */
+ public List<Node> getAdjacencies() {
+ List<Node> adjacencies = new ArrayList<Node>();
+ if (children != null) adjacencies.addAll(children);
+ if (parent != null) adjacencies.add(parent);
+ return adjacencies;
+ }
+
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ private List<Node> children;
+ private Taxon taxon;
+
+ private Node parent;
+ private double height;
+ private double length;
+
+ private Edge edge = null;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/SimpleTree.java b/src/jebl/evolution/trees/SimpleTree.java
new file mode 100644
index 0000000..53588a3
--- /dev/null
+++ b/src/jebl/evolution/trees/SimpleTree.java
@@ -0,0 +1,450 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.AttributableHelper;
+
+import java.util.*;
+
+/**
+ * A basic implementation on an unrooted tree.
+ *
+ * @author Joseph Heled
+ * @version $Id: SimpleTree.java 687 2007-04-10 01:09:24Z stevensh $
+ *
+ */
+
+public final class SimpleTree implements Tree {
+
+ /**
+ * Tree (to be constructed by subsequent calls).
+ */
+ public SimpleTree() {}
+
+ /**
+ * Duplicate a tree.
+ *
+ * @param tree
+ */
+ public SimpleTree(Tree tree) {
+ try {
+ createTree(tree, tree.getExternalNodes().iterator().next(), null);
+ } catch (NoEdgeException e) {
+ throw new IllegalArgumentException("BUG: invalid tree");
+ }
+ }
+
+ /**
+ * Creates a new external node with the given taxon. See createInternalNode
+ * for a description of how to use these methods.
+ * @param taxon the taxon associated with this node
+ * @return the created node reference
+ */
+ public Node createExternalNode(Taxon taxon) {
+ SimpleNode node = new SimpleNode(taxon);
+ externalNodes.put(taxon, node);
+ return node;
+ }
+
+ /**
+ * Once a SimpleTree has been created, the node stucture can be created by
+ * calling createExternalNode and createInternalNode. First of all createExternalNode
+ * is called giving Taxon objects for the external nodes. Then these are put into
+ * sets and passed to createInternalNode to create new internal nodes.
+ *
+ * It is the caller responsibility to insure no cycles are created.
+ *
+ * @param adjacencies the child nodes of this node
+ * @return the created node.
+ */
+ public Node createInternalNode(List<Node> adjacencies) {
+ SimpleNode node = new SimpleNode(adjacencies);
+
+ internalNodes.add(node);
+
+ for( Node c : adjacencies ) {
+ ((SimpleNode)c).addAdjacency(node);
+ }
+ return node;
+ }
+
+ /**
+ * Set edge distance between two adjacent nodes.
+ * @param node1
+ * @param node2
+ * @param length
+ */
+ public void setEdgeLength(final Node node1, final Node node2, final double length) {
+ assert getAdjacencies(node1).contains(node2) && getAdjacencies(node2).contains(node1) && length >= 0;
+
+ final Edge edge = new SimpleEdge(node1, node2, length);
+
+ edges.put(new HashPair<Node>(node1, node2), edge);
+ edges.put(new HashPair<Node>(node2, node1), edge);
+ }
+
+ /**
+ * Change length of an existing edge.
+ * @param edge
+ * @param length
+ */
+ public void setEdgeLength(final Edge edge, final double length) {
+ ((SimpleEdge)edge).length = length;
+ }
+
+ /**
+ * Add a new edge between two existing (non adjacent yet) nodes.
+ * @param node1
+ * @param node2
+ * @param length
+ */
+ public void addEdge(Node node1, Node node2, double length) {
+ assert !getAdjacencies(node1).contains(node2);
+
+ ((SimpleNode)node1).addAdjacency(node2);
+ ((SimpleNode)node2).addAdjacency(node1);
+ setEdgeLength(node1, node2, length);
+ }
+
+ /**
+ * Copy partition of source starting from node but skipping root.
+ *
+ * @param source to copy from
+ * @param node in source to copy
+ * @param root adjacent node already copied
+ * @return copy of node inside tree
+ * @throws NoEdgeException
+ */
+ private Node createTree(Tree source, Node node, Node root) throws NoEdgeException {
+ Node h;
+ if( source.isExternal(node) ) {
+ h = createExternalNode(source.getTaxon(node));
+ } else {
+ h = createInternalNode(new ArrayList<Node>() );
+ }
+
+ final List<Node> adjacencies = source.getAdjacencies(node);
+
+ for( Node c : adjacencies ) {
+ if( c == root ) continue;
+ final Node n = createTree(source, c, node);
+ addEdge(h, n, source.getEdgeLength(node, c) );
+ }
+ return h;
+ }
+
+ /* Graph IMPLEMENTATION */
+
+ /**
+ * Returns a list of edges connected to this node
+ *
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Edge> getEdges(Node node) {
+ //return null;
+ List<Node> adjacencies = getAdjacencies(node);
+ List<Edge> edges = new ArrayList<Edge>();
+ for(Node adjNode : adjacencies){
+ try{
+ edges.add(getEdge(node,adjNode));
+ }
+ catch(NoEdgeException ex){/*do nothing*/}
+ }
+ return edges;
+ }
+
+ /**
+ * @param node
+ * @return the set of nodes that are attached by edges to the given node.
+ */
+ public List<Node> getAdjacencies(Node node) {
+ return ((SimpleNode)node).getAdjacencies();
+ }
+
+ /**
+ * Returns the Edge that connects these two nodes
+ *
+ * @param node1
+ * @param node2
+ * @return the edge object.
+ * @throws jebl.evolution.graphs.Graph.NoEdgeException
+ * if the nodes are not directly connected by an edge.
+ */
+ public Edge getEdge(Node node1, Node node2) throws NoEdgeException {
+ Edge edge = edges.get(new HashPair<Node>(node1, node2));
+ if( edge == null ) {
+ // not connected
+ throw new NoEdgeException();
+ }
+ return edge;
+ }
+
+ /**
+ * @return a set of all nodes that have degree 1.
+ * These nodes are often refered to as 'tips'.
+ */
+ public Set<Node> getExternalNodes() {
+ return new HashSet<Node>(externalNodes.values());
+ }
+
+ /**
+ * @return a set of all nodes that have degree 2 or more.
+ * These nodes are often refered to as internal nodes.
+ */
+ public Set<Node> getInternalNodes() {
+ return new HashSet<Node>(internalNodes);
+ }
+
+ /**
+ * @return the set of taxa associated with the external
+ * nodes of this tree. The size of this set should be the
+ * same as the size of the external nodes set.
+ */
+ public Set<Taxon> getTaxa() {
+ return new HashSet<Taxon>(externalNodes.keySet());
+ }
+ /**
+ * @param node the node whose associated taxon is being requested.
+ * @return the taxon object associated with the given node, or null
+ * if the node is an internal node.
+ */
+ public Taxon getTaxon(Node node) {
+ return ((SimpleNode)node).getTaxon();
+ }
+
+ /**
+ * @param node the node
+ * @return true if the node is of degree 1.
+ */
+ public boolean isExternal(Node node) {
+ return node.getDegree() == 1;
+ }
+
+ /**
+ * @param edge the edge
+ * @return true if the edge has a node of degree 1.
+ */
+ public boolean isExternal(Edge edge) {
+ return ((SimpleEdge)edge).isExternal();
+ }
+
+ /**
+ * @param taxon the taxon
+ * @return the external node associated with the given taxon, or null
+ * if the taxon is not a member of the taxa set associated with this tree.
+ */
+ public Node getNode(Taxon taxon) {
+ return externalNodes.get(taxon);
+ }
+
+ public void renameTaxa(Taxon from, Taxon to) {
+ SimpleNode node = (SimpleNode)externalNodes.get(from);
+ node.setTaxa(to);
+ }
+
+ /**
+ * @param node1
+ * @param node2
+ * @return the length of the edge connecting node1 and node2.
+ * @throws NoEdgeException if the nodes are not directly connected by an edge.
+ */
+ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException {
+ return getEdge(node1, node2).getLength();
+ }
+
+ /**
+ * Returns an array of 2 nodes which are the nodes at either end of the edge.
+ *
+ * @param edge
+ * @return an array of 2 edges
+ */
+ public Node[] getNodes(Edge edge) {
+ return new Node[] { ((SimpleEdge)edge).getNode1(), ((SimpleEdge)edge).getNode2() };
+ }
+
+ /**
+ * @return the set of all nodes in this graph.
+ */
+ public Set<Node> getNodes() {
+ Set<Node> nodes = new HashSet<Node>(internalNodes);
+ nodes.addAll(externalNodes.values());
+ return nodes;
+ }
+
+ /**
+ * @return the set of all edges in this graph.
+ */
+ public Set<Edge> getEdges() {
+ return new HashSet<Edge>(edges.values());
+ }
+
+ /**
+ * @param degree the number of edges connected to a node
+ * @return a set containing all nodes in this graph of the given degree.
+ */
+ public Set<Node> getNodes(int degree) {
+ Set<Node> nodes = new HashSet<Node>();
+ for (Node node : getNodes()) {
+ if (((SimpleNode)node).getDegree() == degree) nodes.add(node);
+ }
+ return nodes;
+ }
+
+ /**
+ * The set of external edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of external edges.
+ */
+ public Set<Edge> getExternalEdges() {
+ Set<Edge> externalEdges = new HashSet<Edge>();
+ for (Edge edge : getEdges()) {
+ if (((SimpleEdge)edge).isExternal()) {
+ externalEdges.add(edge);
+ }
+ }
+ return externalEdges;
+ }
+
+ /**
+ * The set of internal edges. This is a pretty inefficient implementation because
+ * a new set is constructed each time this is called.
+ * @return the set of internal edges.
+ */
+ public Set<Edge> getInternalEdges() {
+ Set<Edge> internalEdges = new HashSet<Edge>();
+ for (Edge edge : getEdges()) {
+ if (!((SimpleEdge)edge).isExternal()) {
+ internalEdges.add(edge);
+ }
+ }
+ return internalEdges;
+ }
+
+ // Attributable IMPLEMENTATION
+
+ public void setAttribute(String name, Object value) {
+ if (helper == null) {
+ helper = new AttributableHelper();
+ }
+ helper.setAttribute(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ if (helper == null) {
+ return null;
+ }
+ return helper.getAttribute(name);
+ }
+
+ public void removeAttribute(String name) {
+ if( helper != null ) {
+ helper.removeAttribute(name);
+ }
+ }
+
+ public Set<String> getAttributeNames() {
+ if (helper == null) {
+ return Collections.emptySet();
+ }
+ return helper.getAttributeNames();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ if (helper == null) {
+ return Collections.emptyMap();
+ }
+ return helper.getAttributeMap();
+ }
+
+ // PRIVATE members
+
+ private AttributableHelper helper = null;
+ private final Set<Node> internalNodes = new HashSet<Node>();
+ private final Map<Taxon, Node> externalNodes = new HashMap<Taxon, Node>();
+ /**
+ * A mapping between edges and edge length.
+ */
+ Map<HashPair, Edge> edges = new HashMap<HashPair, Edge>();
+
+ final class SimpleNode extends BaseNode {
+
+ /**
+ * A tip having a taxon
+ * @param taxon
+ */
+ private SimpleNode(Taxon taxon) {
+ this.adjacencies = Collections.unmodifiableList(new ArrayList<Node>());
+ this.taxon = taxon;
+ }
+
+ /**
+ * An internal node.
+ * @param adjacencies set of adjacent noeds
+ */
+ private SimpleNode(List<Node> adjacencies) {
+ this.adjacencies = Collections.unmodifiableList(adjacencies);
+ this.taxon = null;
+ }
+
+ /**
+ * Add an adjacency.
+ * @param node
+ */
+ public void addAdjacency(Node node) {
+ List<Node> a = new ArrayList<Node>(adjacencies);
+ a.add(node);
+ adjacencies = Collections.unmodifiableList(a);
+ }
+
+ public Taxon getTaxon() {
+ return taxon;
+ }
+
+ public int getDegree() {
+ return (adjacencies == null ? 0 : adjacencies.size());
+ }
+
+ public List<Node> getAdjacencies() {
+ return adjacencies;
+ }
+
+ public void setTaxa(Taxon to) {
+ taxon = to;
+ }
+
+ // PRIVATE members
+ private List<Node> adjacencies;
+ private Taxon taxon;
+
+ }
+
+ final class SimpleEdge extends BaseEdge {
+
+ private SimpleEdge(Node node1, Node node2, double length) {
+ this.node1 = node1;
+ this.node2 = node2;
+ this.length = length;
+ }
+
+ public Node getNode1() {
+ return node1;
+ }
+
+ public Node getNode2() {
+ return node2;
+ }
+
+ public double getLength() {
+ return length;
+ }
+
+ private boolean isExternal() {
+ return (node1.getDegree() == 1 || node2.getDegree() == 1);
+ }
+
+ private double length;
+ Node node1, node2;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/SortedRootedTree.java b/src/jebl/evolution/trees/SortedRootedTree.java
new file mode 100644
index 0000000..43729ca
--- /dev/null
+++ b/src/jebl/evolution/trees/SortedRootedTree.java
@@ -0,0 +1,75 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: SortedRootedTree.java 627 2007-01-15 03:50:40Z pepster $
+ */
+public class SortedRootedTree extends FilteredRootedTree {
+
+ public enum BranchOrdering {
+ INCREASING_NODE_DENSITY("increasing"),
+ DECREASING_NODE_DENSITY("decreasing");
+
+ BranchOrdering(String name) {
+ this.name = name;
+ }
+
+ public String toString() { return name; }
+
+ private String name;
+ }
+
+ public SortedRootedTree(final RootedTree source, BranchOrdering branchOrdering) {
+ super(source);
+ switch (branchOrdering) {
+ case INCREASING_NODE_DENSITY:
+ this.comparator = new Comparator<Node>() {
+ public int compare(Node node1, Node node2) {
+ return jebl.evolution.trees.Utils.getExternalNodeCount(source, node1) -
+ jebl.evolution.trees.Utils.getExternalNodeCount(source, node2);
+ }
+
+ public boolean equals(Node node1, Node node2) {
+ return compare(node1, node2) == 0;
+ }
+ };
+ break;
+ case DECREASING_NODE_DENSITY:
+ this.comparator = new Comparator<Node>() {
+ public int compare(Node node1, Node node2) {
+ return jebl.evolution.trees.Utils.getExternalNodeCount(source, node2) -
+ jebl.evolution.trees.Utils.getExternalNodeCount(source, node1);
+ }
+
+ public boolean equals(Node node1, Node node2) {
+ return compare(node1, node2) == 0;
+ }
+ };
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown enum value");
+ }
+ }
+
+ public SortedRootedTree(RootedTree source, Comparator<Node> comparator) {
+ super(source);
+ this.comparator = comparator;
+ }
+
+ public List<Node> getChildren(Node node) {
+ List<Node> sourceList = source.getChildren(node);
+ Collections.sort(sourceList, comparator);
+ return sourceList;
+ }
+
+ // PRIVATE members
+
+ private final Comparator<Node> comparator;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/SplitSystem.java b/src/jebl/evolution/trees/SplitSystem.java
new file mode 100644
index 0000000..b5ad75e
--- /dev/null
+++ b/src/jebl/evolution/trees/SplitSystem.java
@@ -0,0 +1,118 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.*;
+
+/**
+ * data structure for a set of splits
+ *
+ * @version $Id: SplitSystem.java 317 2006-05-03 23:42:12Z alexeidrummond $
+ *
+ * @author Korbinian Strimmer
+ */
+public class SplitSystem
+{
+ //
+ // Public stuff
+ //
+
+ /**
+ * @param taxa the list of taxa
+ * @param size number of splits
+ */
+ public SplitSystem(final Collection<Taxon> taxa, int size)
+ {
+ this.taxa = Collections.unmodifiableList(new ArrayList<Taxon>(taxa));
+
+ labelCount = taxa.size();
+ splitCount = size;
+
+ splits = new boolean[splitCount][labelCount];
+ }
+
+ /** get number of splits */
+ public int getSplitCount()
+ {
+ return splitCount;
+ }
+
+ /** get number of labels */
+ public int getLabelCount()
+ {
+ return labelCount;
+ }
+
+ /** get split vector */
+ public boolean[][] getSplitVector()
+ {
+ return splits;
+ }
+
+ /** get split */
+ public boolean[] getSplit(int i)
+ {
+ return splits[i];
+ }
+
+
+ /** get taxon list */
+ public List<Taxon> getTaxa() { return taxa; }
+
+ /**
+ + test whether a split is contained in this split system
+ * (assuming the same leaf order)
+ *
+ * @param split split
+ */
+ public boolean hasSplit(boolean[] split)
+ {
+ for (int i = 0; i < splitCount; i++)
+ {
+ if (SplitUtils.isSame(split, splits[i])) return true;
+ }
+
+ return false;
+ }
+
+
+ /** print split system */
+ public String toString()
+ {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ for (int i = 0; i < labelCount; i++)
+ {
+ pw.println(taxa.get(i));
+ }
+ pw.println();
+
+
+ for (int i = 0; i < splitCount; i++)
+ {
+ for (int j = 0; j < labelCount; j++)
+ {
+ if (splits[i][j] == true)
+ pw.print('*');
+ else
+ pw.print('.');
+ }
+
+ pw.println();
+ }
+
+ return sw.toString();
+ }
+
+
+ //
+ // Private stuff
+ //
+
+ private int labelCount, splitCount;
+ private List<Taxon> taxa;
+ private boolean[][] splits;
+}
diff --git a/src/jebl/evolution/trees/SplitUtils.java b/src/jebl/evolution/trees/SplitUtils.java
new file mode 100644
index 0000000..c793c66
--- /dev/null
+++ b/src/jebl/evolution/trees/SplitUtils.java
@@ -0,0 +1,140 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * utilities for split systems
+ *
+ * @version $Id: SplitUtils.java 545 2006-11-28 00:08:34Z twobeers $
+ *
+ * @author Korbinian Strimmer
+ */
+public class SplitUtils {
+ //
+ // Public stuff
+ //
+
+ /**
+ * creates a split system from a tree
+ * (using tree-induced order of sequences)
+ *
+ * @param tree
+ */
+ public static SplitSystem getSplits(Tree tree) {
+ return getSplits(new ArrayList<Taxon>(tree.getTaxa()), tree);
+ }
+
+ /**
+ * creates a split system from a tree
+ * (using a pre-specified order of sequences)
+ *
+ * @param taxa the list of taxa (order is important)
+ * @param tree
+ */
+ public static SplitSystem getSplits(List<Taxon> taxa, Tree tree)
+ {
+ int size = tree.getInternalEdges().size();
+ SplitSystem splitSystem = new SplitSystem(taxa, size);
+
+ boolean[][] splits = splitSystem.getSplitVector();
+
+ int j = 0;
+ for (Edge edge : tree.getInternalEdges()) {
+ getSplit(taxa, tree, edge, splits[j]);
+ j++;
+ }
+
+ return splitSystem;
+ }
+
+
+ /**
+ * get split for branch associated with internal node
+ *
+ * @param taxa order of labels
+ * @param tree Tree
+ * @param edge Edge
+ * @param split
+ */
+ public static void getSplit(List<Taxon> taxa, Tree tree, Edge edge, boolean[] split) {
+
+ // make sure split is reset
+ for (int i = 0; i < split.length; i++) {
+ split[i] = false;
+ }
+
+ // mark all leafs downstream of the node
+ Node[] nodes = tree.getNodes(edge);
+
+ markNode(taxa, tree, nodes[0], nodes[1], split);
+
+ // standardize split (i.e. first index is alway true)
+ if (split[0] == false) {
+ for (int i = 0; i < split.length; i++) {
+ if (split[i] == false)
+ split[i] = true;
+ else
+ split[i] = false;
+ }
+ }
+ }
+
+ /**
+ * Checks two splits for identity. This method assumes that the
+ * two splits are of the same length and use the same leaf order/
+ *
+ * @param s1 split 1
+ * @param s2 split 2
+ * @return true if the two splits are identical
+ * @throws IllegalArgumentException if splits don't have the same length
+ */
+ public static boolean isSame(boolean[] s1, boolean[] s2)
+ {
+ boolean reverse = (s1[0] != s2[0]);
+
+ if (s1.length != s2.length)
+ throw new IllegalArgumentException("Splits must be of the same length!");
+
+ for (int i = 0; i < s1.length; i++) {
+ if (reverse) {
+ // splits not identical
+ if (s1[i] == s2[i]) return false;
+ }
+ else {
+ // splits not identical
+ if (s1[i] != s2[i]) return false;
+ }
+ }
+
+ return true;
+ }
+
+ //
+ // Package stuff
+ //
+
+ static void markNode(List<Taxon> taxa, Tree tree, Node node, Node parent, boolean[] split) {
+ if (tree.isExternal(node)) {
+ Taxon taxon = tree.getTaxon(node);
+ int index = taxa.indexOf(taxon);
+
+ if (index < 0) {
+ throw new IllegalArgumentException("INCOMPATIBLE IDENTIFIER (" + taxon + ")");
+ }
+
+ split[index] = true;
+ }
+ else {
+ for (Node child : tree.getAdjacencies(node)) {
+ if (child != parent) {
+ markNode(taxa, tree, child, node, split);
+ }
+ }
+ }
+ }
+}
diff --git a/src/jebl/evolution/trees/TransformedRootedTree.java b/src/jebl/evolution/trees/TransformedRootedTree.java
new file mode 100644
index 0000000..48fe9e7
--- /dev/null
+++ b/src/jebl/evolution/trees/TransformedRootedTree.java
@@ -0,0 +1,110 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+
+/**
+ * This RootedTree class wraps another RootedTree and transforms
+ * the branch lengths and node heights using various functions.
+ * Currently implemented are equal lengths (all branch lengths
+ * are 1.0) and cladogram (the height of a node is proportional
+ * to the number of external nodes). Note that all these functions
+ * are recalculated on the fly for every call to getHeight and
+ * getLength and it may be desirable to precalculate and cache them.
+ * @author Andrew Rambaut
+ * @version $Id: TransformedRootedTree.java 545 2006-11-28 00:08:34Z twobeers $
+ */
+public class TransformedRootedTree extends FilteredRootedTree {
+
+ public enum Transform {
+ EQUAL_LENGTHS("equal"),
+ CLADOGRAM("cladogram"),
+ PROPORTIONAL("proportional");
+
+ Transform(String name) {
+ this.name = name;
+ }
+
+ public String toString() { return name; }
+
+ private String name;
+ }
+
+ public TransformedRootedTree(final RootedTree source, Transform transform) {
+ super(source);
+ this.transform = transform;
+ }
+
+ public boolean hasHeights() {
+ return true;
+ }
+
+ public double getHeight(Node node) {
+ switch (transform) {
+ case EQUAL_LENGTHS:
+ int treeLength = getMaxPathLength(getRootNode());
+ int rootPathLength = getPathLengthToRoot(node);
+ return treeLength - rootPathLength;
+ case CLADOGRAM:
+ return getMaxPathLength(node);
+ case PROPORTIONAL:
+ return getCladeSize(node) - 1;
+ default:
+ throw new IllegalArgumentException("Unknown enum value");
+ }
+ }
+
+ public boolean hasLengths() {
+ return true;
+ }
+
+ public double getLength(Node node) {
+ switch (transform) {
+ case EQUAL_LENGTHS:
+ return 1.0;
+ case CLADOGRAM:
+ case PROPORTIONAL:
+ Node parent = getParent(node);
+ if (parent == null) return 0.0; // is the root
+ return getHeight(parent) - getHeight(node);
+ default:
+ throw new IllegalArgumentException("Unknown enum value");
+ }
+ }
+
+ private int getCladeSize(Node node) {
+ if (isExternal(node)) {
+ return 1;
+ }
+ int size = 0;
+ for (Node child : getChildren(node)) {
+ size += getCladeSize(child);
+ }
+ return size;
+ }
+
+ private int getMaxPathLength(Node node) {
+ if (isExternal(node)) {
+ return 0;
+ }
+ int maxPathLength = 0;
+ for (Node child : getChildren(node)) {
+ int pathLength = getMaxPathLength(child);
+ if (pathLength > maxPathLength) {
+ maxPathLength = pathLength;
+ }
+ }
+ return maxPathLength + 1;
+ }
+
+ private int getPathLengthToRoot(Node node) {
+ int pathLength = 0;
+ Node parent = getParent(node);
+ while (parent != null) {
+ pathLength++;
+ parent = getParent(parent);
+ }
+ return pathLength;
+ }
+
+ private final Transform transform;
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/Tree.java b/src/jebl/evolution/trees/Tree.java
new file mode 100644
index 0000000..ab2c957
--- /dev/null
+++ b/src/jebl/evolution/trees/Tree.java
@@ -0,0 +1,84 @@
+/*
+ * Tree.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Edge;
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.Set;
+
+/**
+ * A rooted or unrooted tree. This interface is the common base class for all trees,
+ * and contains only operations for unrooted trees. The subinterface RootedTree
+ * contains additional methods that make sense only on rooted trees.
+ *
+ * Both interfaces contain no mutator methods. As of 2006-12-08, the only way
+ * to mutate a tree after it has been built is to use its concrete class
+ * instead of the Tree or RootedTree interface.
+ *
+ * @author rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Tree.java 627 2007-01-15 03:50:40Z pepster $
+ */
+public interface Tree extends Graph {
+
+ /**
+ * @return a set of all nodes that have degree 1.
+ * These nodes are often refered to as 'tips'.
+ */
+ Set<Node> getExternalNodes();
+
+ /**
+ * @return a set of all nodes that have degree 2 or more.
+ * These nodes are often refered to as internal nodes.
+ */
+ Set<Node> getInternalNodes();
+
+ /**
+ * @return a set of all edges that have a degree 1 node.
+ */
+ Set<Edge> getExternalEdges();
+
+ /**
+ * @return a set of all edges for which both nodes have degree 2 or more.
+ */
+ Set<Edge> getInternalEdges();
+
+ /**
+ * @return the set of taxa associated with the external
+ * nodes of this tree. The size of this set should be the
+ * same as the size of the external nodes set.
+ */
+ Set<Taxon> getTaxa();
+
+ /**
+ * @param node the node whose associated taxon is being requested.
+ * @return the taxon object associated with the given node, or null
+ * if the node is an internal node.
+ */
+ Taxon getTaxon(Node node);
+
+ /**
+ * @param node the node
+ * @return true if the node is of degree 1.
+ */
+ boolean isExternal(Node node);
+
+ /**
+ * @param taxon the taxon
+ * @return the external node associated with the given taxon, or null
+ * if the taxon is not a member of the taxa set associated with this tree.
+ */
+ Node getNode(Taxon taxon);
+
+ void renameTaxa(Taxon from, Taxon to);
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/trees/TreeBiPartitionInfo.java b/src/jebl/evolution/trees/TreeBiPartitionInfo.java
new file mode 100644
index 0000000..bb80f53
--- /dev/null
+++ b/src/jebl/evolution/trees/TreeBiPartitionInfo.java
@@ -0,0 +1,131 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.util.FixedBitSet;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Work in progress
+ * @author Joseph Heled
+ * @version $Id: TreeBiPartitionInfo.java 531 2006-11-17 00:59:01Z pepster $
+ */
+public class TreeBiPartitionInfo {
+ class BiPartiotionInfo {
+ BiPartiotionInfo(Node n) {
+ this.n = n;
+ }
+
+ Node n;
+ public boolean has;
+ }
+
+ final List<Taxon> taxa;
+ final RootedTree t;
+ final int nTips;
+ HashMap<FixedBitSet, BiPartiotionInfo> all;
+
+ public TreeBiPartitionInfo(RootedTree t, List<Taxon> taxa) {
+ this.t = t;
+ this.taxa = taxa;
+ nTips = t.getExternalNodes().size();
+ all = new HashMap<FixedBitSet, BiPartiotionInfo>();
+ forNode(t.getRootNode());
+ }
+
+
+// BiPartiotionInfo forNode(Node n) {
+// final BiPartiotionInfo p = new BiPartiotionInfo(n);
+// if( t.isExternal(n) ) {
+// final int pos = taxa.indexOf(t.getTaxon(n));
+// p.partition.set(pos);
+//
+// } else {
+//
+// for( Node c : t.getChildren(n) ) {
+// final TreeBiPartitionInfo.BiPartiotionInfo info = forNode(c);
+// p.partition.union(info.partition);
+// }
+// }
+// if( ! p.partition.contains(0) ) {
+// p.partition.complement();
+// }
+// all.put(p.partition, n);
+// return p;
+// }
+//
+
+ private FixedBitSet forNode(Node n) {
+ final FixedBitSet p = new FixedBitSet(nTips);
+ if( t.isExternal(n) ) {
+ final int pos = taxa.indexOf(t.getTaxon(n));
+ p.set(pos);
+
+ } else {
+
+ for( Node c : t.getChildren(n) ) {
+ final FixedBitSet info = forNode(c);
+ p.union(p);
+ }
+ }
+ if( t.getParent(n) != t.getRootNode() && ! p.contains(0) ) {
+ p.complement();
+ }
+ all.put(p, new BiPartiotionInfo(n));
+ return p;
+ }
+
+ public enum DistanceNorm {
+ NORM1,
+ NORM2
+ }
+
+ public static double distance(TreeBiPartitionInfo t1, TreeBiPartitionInfo t2, DistanceNorm norm) {
+ double d = 0.0;
+ for( BiPartiotionInfo k : t2.all.values() ) {
+ k.has = false;
+ }
+ double din = 0;
+ double dout = 0;
+
+ for( Map.Entry<FixedBitSet, BiPartiotionInfo> k : t1.all.entrySet() ) {
+ final BiPartiotionInfo info = t2.all.get(k.getKey());
+ final double b1 = t1.t.getLength(k.getValue().n);
+ double dif;
+ if( info != null ) {
+
+ final double b2 = t2.t.getLength(info.n);
+ info.has = true;
+
+ dif = Math.abs(b1 - b2);
+ } else {
+ dif = b1;
+ }
+ if( norm == DistanceNorm.NORM1 ) {
+ //d += dif;
+ din += dif;
+ } else {
+ //d += dif * dif;
+ din += dif * dif;
+ }
+ }
+
+ for( BiPartiotionInfo info : t2.all.values() ) {
+ if( !info.has ) {
+ final double dif = t2.t.getLength(info.n);
+ if( norm == DistanceNorm.NORM1 ) {
+ //d += dif;
+ dout += dif;
+ } else {
+ //d += dif * dif;
+ dout += dif * dif;
+ }
+ }
+ }
+ d = din + dout;
+ return ( norm == DistanceNorm.NORM1 ) ? d : Math.sqrt(d);
+ }
+}
diff --git a/src/jebl/evolution/trees/TreeBuilder.java b/src/jebl/evolution/trees/TreeBuilder.java
new file mode 100644
index 0000000..7e6e91d
--- /dev/null
+++ b/src/jebl/evolution/trees/TreeBuilder.java
@@ -0,0 +1,17 @@
+package jebl.evolution.trees;
+
+import jebl.util.ProgressListener;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: TreeBuilder.java 301 2006-04-17 15:35:01Z rambaut $
+ */
+public interface TreeBuilder<T extends Tree> {
+
+ T build();
+
+ void addProgressListener(ProgressListener listener);
+
+ void removeProgressListener(ProgressListener listener);
+}
diff --git a/src/jebl/evolution/trees/TreeBuilderFactory.java b/src/jebl/evolution/trees/TreeBuilderFactory.java
new file mode 100644
index 0000000..2e8e4a5
--- /dev/null
+++ b/src/jebl/evolution/trees/TreeBuilderFactory.java
@@ -0,0 +1,130 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.taxa.Taxon;
+
+/**
+ * A meeting point for tree building from sequence data. A very initial form which will develope to encompass more
+ * methods and distances. Currently only pairwise distance methods are implemented.
+ *
+ * @author Joseph Heled
+ * @version $Id: TreeBuilderFactory.java 662 2007-03-21 00:32:24Z twobeers $
+ *
+ */
+
+public class TreeBuilderFactory {
+
+ /**
+ * Supported methods for tree building
+ */
+ public enum Method { NEIGHBOR_JOINING("Neighbor-Joining"), UPGMA("UPGMA");
+ Method(String name) { this.name = name; }
+ public String toString() { return getName(); }
+ public String getName() { return name; }
+ private String name;
+ }
+
+ /**
+ * Supported pairwise distance methods
+ */
+ public enum DistanceModel { JukesCantor, F84, HKY, TamuraNei }
+
+ /**
+ * Supported consensus methods.
+ */
+ public enum ConsensusMethod { GREEDY, MRCAC }
+
+ /**
+ *
+ * @param method to check
+ * @return Wheather method generates a rooted or unrooted tree.
+ */
+ public static boolean isRootedMethod(Method method) {
+ switch( method ) {
+ case UPGMA:
+ {
+ return true;
+ }
+ case NEIGHBOR_JOINING:
+ default:
+ {
+ return false;
+ }
+ }
+ }
+
+ // TT: This method should probably have been called createBuilder() because it
+ // creates a new builder every time instead of reusing an existing one. This
+ // makes a difference because it means that progress listeners added to a
+ // builder don't need to be removed afterwards if the builder is discarded.
+
+ /**
+ *
+ * @param method build method to use.
+ * @param distances Pre computed pairwise distances.
+ * @return A tree builder using method and distance matrix
+ */
+ static public ClusteringTreeBuilder getBuilder(Method method, DistanceMatrix distances) {
+ ClusteringTreeBuilder builder;
+ switch( method ) {
+ case UPGMA:
+ {
+ builder = new UPGMATreeBuilder(distances);
+ break;
+ }
+ case NEIGHBOR_JOINING:
+ default:
+ {
+ builder = new NeighborJoiningTreeBuilder(distances);
+ break;
+ }
+ }
+ return builder;
+ }
+
+ static public ConsensusTreeBuilder buildUnRooted(Tree[] trees, Taxon outGroup, double supportThreshold, ConsensusMethod method) {
+ if( ! (supportThreshold >= 0 && supportThreshold <= 1) ) {
+ throw new IllegalArgumentException("support not in [01]");
+ }
+ switch( method ) {
+ case GREEDY: {
+ return new GreedyUnrootedConsensusTreeBuilder(trees, outGroup, supportThreshold);
+ }
+ }
+ // bug
+ throw new IllegalArgumentException(method.toString());
+ }
+
+ static public ConsensusTreeBuilder buildRooted(RootedTree[] trees, double supportThreshold, ConsensusMethod method) {
+ if( ! (supportThreshold >= 0 && supportThreshold <= 1) ) {
+ throw new IllegalArgumentException("support not in [01]");
+ }
+
+ switch( method ) {
+ case GREEDY: {
+ return new GreedyRootedConsensusTreeBuilder(trees, supportThreshold);
+ }
+ case MRCAC: {
+ return new MRCACConsensusTreeBuilder(trees, supportThreshold);
+ }
+ }
+ // bug
+ throw new IllegalArgumentException(method.toString());
+ }
+
+ /**
+ * convenience method. Convert arrays of trees, guaranteed to be rooted to the array of the appropriate
+ * type.
+ * @param trees trees - all must be rooted
+ * @param supportThreshold minimum required consensus support (in [01])
+ * @param method which consensus method to use
+ * @return consensus tree builder
+ */
+ static public ConsensusTreeBuilder buildRooted(Tree[] trees, double supportThreshold, ConsensusMethod method) {
+ RootedTree[] rtrees = new RootedTree[trees.length];
+ for(int i = 0; i < trees.length; ++i) {
+ rtrees[i] = (RootedTree)trees[i];
+ }
+ return buildRooted(rtrees, supportThreshold, method);
+ }
+}
diff --git a/src/jebl/evolution/trees/UPGMATreeBuilder.java b/src/jebl/evolution/trees/UPGMATreeBuilder.java
new file mode 100644
index 0000000..bbb4302
--- /dev/null
+++ b/src/jebl/evolution/trees/UPGMATreeBuilder.java
@@ -0,0 +1,71 @@
+package jebl.evolution.trees;
+
+import jebl.evolution.distances.DistanceMatrix;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * constructs a UPGMA tree from pairwise distances
+ *
+ * @version $Id: UPGMATreeBuilder.java 301 2006-04-17 15:35:01Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Joseph Heled
+ *
+ * Adapted from BEAST code.
+ */
+
+class UPGMATreeBuilder extends ClusteringTreeBuilder {
+ // want a rooted tree
+ private final SimpleRootedTree tree;
+
+ /**
+ * constructor UPGMA tree
+ *
+ * @param distanceMatrix distance matrix
+ */
+ public UPGMATreeBuilder(DistanceMatrix distanceMatrix) {
+ super(distanceMatrix, 2);
+ tree = new SimpleRootedTree();
+ }
+
+ //
+ // Protected and Private stuff
+ //
+
+ protected Tree getTree() {
+ return tree;
+ }
+
+ protected Node createExternalNode(Taxon taxon) {
+ return tree.createExternalNode(taxon);
+ }
+
+ protected Node createInternalNode(Node[] nodes, double[] distances) {
+ List<Node> a = Arrays.asList(nodes);
+ Node node = tree.createInternalNode(a);
+ tree.setHeight(node, distances[0]);
+ return node;
+ }
+
+ protected double[] joinClusters() {
+ Double d = getDist(besti, bestj) / 2.0;
+ return new double[] {d};
+ }
+
+ protected double updatedDistance(int k) {
+ int i = besti;
+ int j = bestj;
+ int ai = alias[i];
+ int aj = alias[j];
+
+ double tipSum = (double) (tipCount[ai] + tipCount[aj]);
+
+ return (((double)tipCount[ai]) / tipSum) * getDist(k, i) +
+ (((double)tipCount[aj]) / tipSum) * getDist(k, j);
+ }
+}
diff --git a/src/jebl/evolution/trees/Utils.java b/src/jebl/evolution/trees/Utils.java
new file mode 100644
index 0000000..fb94204
--- /dev/null
+++ b/src/jebl/evolution/trees/Utils.java
@@ -0,0 +1,680 @@
+/*
+ * Utils.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.trees;
+
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+
+import java.util.*;
+
+/**
+ * A collection of utility functions for trees.
+ *
+ * @author rambaut
+ * @author Alexei Drummond
+ * @version $Id: Utils.java 720 2007-06-01 01:36:21Z stevensh $
+ */
+public final class Utils {
+
+ /**
+ * @param tree
+ * @return the rooted tree as a newick format string
+ */
+ public static String toNewick(RootedTree tree) {
+ StringBuilder buffer = new StringBuilder();
+ toNewick(tree, tree.getRootNode(), buffer);
+ return buffer.toString();
+ }
+
+
+ /**
+ * Constructs a unique newick representation of a tree
+ *
+ * @param tree
+ */
+ public static String toUniqueNewick(RootedTree tree) {
+ return toUniqueNewick(tree, tree.getRootNode());
+ }
+
+ /**
+ * Constructs a unique newick representation of a tree print only an attribute
+ *
+ * @param tree
+ */
+ public static String toUniqueNewickByAttribute(RootedTree tree, String attribute) {
+ return toUniqueNewickByAttribute(tree, tree.getRootNode(), attribute);
+ }
+
+// private static void addMetaComment(Node node, StringBuilder buffer) {
+// Map<String, Object> map = node.getAttributeMap();
+// if (map.size() == 0) {
+// return;
+// }
+// buffer.append(" [&");
+// boolean first = true;
+// for (Map.Entry<String, Object> o : map.entrySet()) {
+// if (! first) {
+// buffer.append(",");
+// }
+// first = false;
+//
+// String val = o.getValue().toString();
+// // we have no way to quote commas right now, throw them away if inside value.
+// val = val.replace(',', ' ');
+// buffer.append(o.getKey()).append("=").append(val);
+// }
+// buffer.append("] ");
+// }
+
+// Andrew - Comments are not part of the Newick format so should not be included except within
+// a NEXUS file. I have copied the tree writing code (with metacomments) to NexusExport and
+
+ // simplified this on to produce the straight Newick format.
+
+ private static void toNewick(RootedTree tree, Node node, StringBuilder buffer) {
+ if (tree.isExternal(node)) {
+ String name = tree.getTaxon(node).getName();
+ if (!name.matches("^(\\w|-)+$")) {
+ name = "\'" + name + "\'";
+ }
+ buffer.append(name);
+ if (tree.hasLengths()) {
+ buffer.append(':');
+ buffer.append(tree.getLength(node));
+ }
+ } else {
+ buffer.append('(');
+ List<Node> children = tree.getChildren(node);
+ final int last = children.size() - 1;
+ for (int i = 0; i < children.size(); i++) {
+ toNewick(tree, children.get(i), buffer);
+ buffer.append(i == last ? ')' : ',');
+ }
+
+ Node parent = tree.getParent(node);
+ // Don't write root length. This is ignored elsewhere and the nexus importer fails
+ // whet it is present.
+ if (parent != null && tree.hasLengths()) {
+ buffer.append(":").append(tree.getLength(node));
+ }
+ }
+ }
+
+ private static void branchesMinMax(RootedTree tree, Node node, double[] bounds) {
+ if (tree.isExternal(node)) {
+ return;
+ }
+
+ if (!tree.hasLengths()) {
+ bounds[0] = bounds[1] = 1;
+ return;
+ }
+
+ final List<Node> children = tree.getChildren(node);
+ for (Node n : children) {
+ final double len = tree.getLength(n);
+ bounds[0] = Math.min(bounds[0], len);
+ bounds[1] = Math.max(bounds[1], len);
+ branchesMinMax(tree, n, bounds);
+ }
+ }
+
+ private static String[] asText(RootedTree tree, Node node, final double factor) {
+ if (tree.isExternal(node)) {
+ String name = tree.getTaxon(node).getName();
+ return new String[]{' ' + name};
+ }
+
+ final List<Node> children = tree.getChildren(node);
+ List<String[]> a = new ArrayList<String[]>(children.size());
+ int[] branches = new int[children.size()];
+ int tot = 0;
+ int maxHeight = -1;
+
+ int k = 0;
+ for (Node n : children) {
+ String[] s = asText(tree, n, factor);
+ tot += s.length;
+ final double len = tree.hasLengths() ? tree.getLength(n) : 1.0;
+ // set 1 as lower bound for branch since the vertical connector
+ // (which theoretically has zero width) takes one line.
+ final int branchLen = Math.max((int) Math.round(len * factor), 1);
+ branches[k] = branchLen;
+ ++k;
+ maxHeight = Math.max(maxHeight, s[0].length() + branchLen);
+ a.add(s);
+ }
+ // one empty line between sub trees
+ tot += children.size() - 1;
+
+ ArrayList<String> x = new ArrayList<String>(tot);
+ for (int i = 0; i < a.size(); ++i) {
+ String[] s = a.get(i);
+ int branchIndex = s.length / 2;
+ boolean isLast = i == a.size() - 1;
+ for (int j = 0; j < s.length; ++j) {
+ char c = (j == branchIndex) ? '=' : ' ';
+ char l = (i == 0 && j < branchIndex || isLast && j > branchIndex) ? ' ' :
+ (j == branchIndex ? '+' : '|');
+ String l1 = l + rep(c, branches[i] - 1) + s[j];
+ x.add(l1 + rep(' ', maxHeight - l1.length()));
+ }
+ if (!isLast) {
+ x.add('|' + rep(' ', maxHeight - 1));
+ }
+ }
+
+ for (String ss : x) {
+ assert(ss.length() == x.get(0).length());
+ }
+
+ return x.toArray(new String[]{});
+
+ }
+
+ private static String rep(char c, int count) {
+ final StringBuilder b = new StringBuilder();
+ while (count > 0) {
+ b.append(c);
+ --count;
+ }
+ return b.toString();
+ }
+
+ // Number of branches from node to most remote tip.
+ private static int nodeDistance(final RootedTree tree, final Node node) {
+ if (tree.isExternal(node)) {
+ return 0;
+ }
+
+ int d = 0;
+ for (Node n : tree.getChildren(node)) {
+ d = Math.max(d, nodeDistance(tree, n));
+ }
+ return d + 1;
+ }
+
+ public static double safeNodeHeight(final RootedTree tree, final Node node) {
+ if (tree.hasHeights()) {
+ return tree.getHeight(node);
+ }
+ return nodeDistance(tree, node);
+ }
+
+ private static double safeTreeHeight(final RootedTree tree) {
+ return safeNodeHeight(tree, tree.getRootNode());
+ }
+
+ public static int maxLevels(final RootedTree tree) {
+ return nodeDistance(tree, tree.getRootNode());
+ }
+
+ public static String[] asText(Tree tree, int widthGuide) {
+ RootedTree rtree = rootTheTree(tree);
+
+ Node root = rtree.getRootNode();
+ double[] bounds = new double[2];
+ bounds[0] = java.lang.Double.MAX_VALUE;
+ bounds[1] = -1;
+
+ branchesMinMax(rtree, root, bounds);
+ double lowBound = 2 / bounds[0];
+ double treeHeight = safeTreeHeight(rtree);
+ double treeHieghtWithLowBound = treeHeight * lowBound;
+
+ double scale;
+ if (treeHieghtWithLowBound > widthGuide) {
+ scale = widthGuide / treeHeight;
+ } else {
+ lowBound = (5 / bounds[0]);
+ if (treeHeight * lowBound <= widthGuide) {
+ scale = lowBound;
+ } else {
+ scale = widthGuide / treeHeight;
+ }
+ }
+ return asText(rtree, root, scale);
+ }
+
+ private static double dist(Tree tree, Node root, Node node, Map<HashPair<Node>, Double> dists) throws Graph.NoEdgeException {
+ HashPair<Node> p = new HashPair<Node>(root, node);
+ if (dists.containsKey(p)) {
+ return dists.get(p);
+ }
+
+ // assume positive branches
+ double maxDist = 0;
+ for (Node n : tree.getAdjacencies(node)) {
+ if (n != root) {
+ double d = dist(tree, node, n, dists);
+ maxDist = Math.max(maxDist, d);
+ }
+ }
+ double dist = tree.getEdgeLength(node, root) + maxDist;
+
+ dists.put(p, dist);
+ return dist;
+ }
+
+ /**
+ * Return a rooted tree from any tree.
+ * <p/>
+ * If tree already rooted, return it. Otherwise if there is a "natuarl root" (i.e. a node of
+ * degree 2) use it as root. Otherwise use an internal node close to the center of the tree as a root.
+ *
+ * @param tree to root
+ * @return rooted representation
+ */
+ public static RootedTree rootTheTree(Tree tree) {
+ // If already rooted, do nothing
+ if (tree instanceof RootedTree) {
+ return (RootedTree) tree;
+ }
+
+ // If a natural root exists, root there
+ Set<Node> d2 = tree.getNodes(2);
+ if (d2.size() == 1) {
+ return new RootedFromUnrooted(tree, d2.iterator().next(), true);
+ }
+
+ RootedTree rtree = rootTreeAtCenter(tree);
+ assert Graph.Utils.getDegree(rtree, rtree.getRootNode()) == 2;
+
+ // Root at central internal node. The root of the tree has at least 3 children.
+ // WARNING: using the implementation fact that childern of RootedFromUnrooted are in fact nodes from tree.
+
+ Node root = null;
+ double minLength = 100;
+ for (Node n : rtree.getChildren(rtree.getRootNode())) {
+ if (!rtree.isExternal(n)) {
+ final double length = rtree.getLength(n);
+ if (root == null || length < minLength) {
+ minLength = length;
+ root = n;
+ }
+
+ }
+ }
+
+ return new RootedFromUnrooted(tree, root, true);
+ }
+
+ /**
+ * Root any tree by locating the "center" of tree and adding a new root node at that point
+ * <p/>
+ * for any point on the tree x let D(x) = Max{distance between x and t : for all tips t}
+ * The "center" c is the point with the smallest distance, i.e. D(c) = min{ D(x) : x in tree }
+ *
+ * @param tree to root
+ * @return rooted tree
+ */
+ public static RootedTree rootTreeAtCenter(Tree tree) {
+ // Method - find the pair of tips with the longest distance. It is easy to see that the center
+ // is at the midpoint of the path between them.
+
+ HashMap<HashPair<Node>, Double> dists = new HashMap<HashPair<Node>, Double>();
+ try {
+ double maxDistance = -Double.MAX_VALUE;
+ // node on maximal path
+ Node current = null;
+ // next node on maximal path
+ Node direction = null;
+
+ // locate one terminal node of longest path
+ for (Node e : tree.getExternalNodes()) {
+ for (Node n : tree.getAdjacencies(e)) {
+ final double d = dist(tree, e, n, dists);
+ if (d > maxDistance) {
+ maxDistance = d;
+ current = e;
+ direction = n;
+ }
+ }
+ }
+
+ // traverse along maximal path to it's middle
+ double distanceLeft = maxDistance / 2.0;
+
+ while (true) {
+ final double len = tree.getEdgeLength(current, direction);
+ if (distanceLeft <= len) {
+ //System.out.println(toNewick(rtree));
+ return new RootedFromUnrooted(tree, current, direction, distanceLeft);
+ }
+ distanceLeft -= len;
+
+ maxDistance = -Double.MAX_VALUE;
+ Node next = null;
+ for (Node n : tree.getAdjacencies(direction)) {
+ if (n == current) continue;
+ final double d = dist(tree, direction, n, dists);
+ if (d > maxDistance) {
+ maxDistance = d;
+ next = n;
+ }
+ }
+ current = direction;
+ direction = next;
+ }
+ } catch (Graph.NoEdgeException e1) {
+ return null; // serious bug, should not happen
+ }
+ }
+
+ /**
+ * @param tree the tree
+ * @param node1
+ * @param node2
+ * @return the path length between the two nodes
+ */
+ public static double getPathLength(Tree tree, Node node1, Node node2) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * @param rootedTree the rooted tree
+ * @return true if all internal nodes in the given tree are of degree 3, except the root
+ * which must have a degree of 2.
+ */
+ public static boolean isBinary(RootedTree rootedTree) {
+
+ return (rootedTree.getNodes(3).size() == (rootedTree.getInternalNodes().size() - 1))
+ && (Tree.Utils.getDegree(rootedTree, rootedTree.getRootNode()) == 2);
+ }
+
+
+ /**
+ * @param rootedTree the rooted tree
+ * @return true if all the external nodes in the tree have a height of 0.0
+ */
+ public static boolean isUltrametric(RootedTree rootedTree) {
+
+ Set externalNodes = rootedTree.getExternalNodes();
+ for (Object externalNode : externalNodes) {
+ Node node = (Node) externalNode;
+ if (rootedTree.getHeight(node) != 0.0) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return the number of external nodes under this node.
+ *
+ * @param tree
+ * @param node
+ * @return the number of external nodes under this node.
+ */
+ public static int getExternalNodeCount(RootedTree tree, Node node) {
+
+ final List<Node> children = tree.getChildren(node);
+ if (children.size() == 0) return 1;
+
+ int externalNodeCount = 0;
+ for (Node child : children) {
+ externalNodeCount += getExternalNodeCount(tree, child);
+ }
+
+ return externalNodeCount;
+ }
+
+ /**
+ * All nodes in subtree - parents before children (pre - order).
+ *
+ * @param tree
+ * @param node
+ * @return nodes in pre-order
+ */
+ public static List<Node> getNodes(RootedTree tree, Node node) {
+ final List<Node> nodes = new ArrayList<Node>();
+ nodes.add(node);
+
+ for (Node child : tree.getChildren(node)) {
+ nodes.addAll(getNodes(tree, child));
+ }
+
+ return nodes;
+ }
+
+ /**
+ * Right Neighbour of a tip (taxon).
+ * <p/>
+ * When tree is laid with children in given order, this would be the taxon to the right.
+ *
+ * @param tree
+ * @param tipNode
+ * @return Right Neighbour. null if node is the rightmost in tree or not a tip.
+ */
+ public static Node rightNb(RootedTree tree, Node tipNode) {
+ if (!tree.isExternal(tipNode)) return null;
+
+ // Go up to the first ancestor of tip so that tip is not in the rightmost (last) sub tree
+ List<Node> children;
+ int loc;
+ Node parent = tipNode; // start th loop below with correct node
+ do {
+ tipNode = parent;
+ parent = tree.getParent(tipNode);
+ if (parent == null) return null; // rightmost in tree
+ children = tree.getChildren(parent);
+ loc = children.indexOf(tipNode);
+ } while (loc == children.size() - 1);
+
+ assert(loc < children.size() - 1);
+
+ // now find the leftmost tip down the sub tree to the right of ancestor
+ Node n = children.get(loc + 1);
+ while (!tree.isExternal(n)) {
+ n = tree.getChildren(n).get(0);
+ }
+ return n;
+ }
+
+ /**
+ * Left Neighbour of a tip (taxon).
+ * <p/>
+ * When tree is laid with children in given order, this would be the taxon to the left.
+ *
+ * @param tree
+ * @param node
+ * @return Left Neighbour. null if node is the leftmost in tree or not a tip.
+ */
+ public static Node leftNb(RootedTree tree, Node node) {
+ if (!tree.isExternal(node)) return null;
+
+ // Go up to the first ancestor of tip so that tip is not in the first sub tree
+ Node parent = node;
+ List<Node> children;
+ int loc;
+ do {
+ node = parent;
+ parent = tree.getParent(node);
+ if (parent == null) return null; // rightmost in tree
+ children = tree.getChildren(parent);
+ loc = children.indexOf(node);
+ } while (loc == 0);
+
+ assert(loc > 0);
+
+ // now find the rightmost tip down the sub tree to the left of ancestor
+
+ Node n = children.get(loc - 1);
+ while (!tree.isExternal(n)) {
+ final List<Node> ch = tree.getChildren(n);
+ n = ch.get(ch.size() - 1);
+ }
+ return n;
+ }
+
+ /**
+ * @param tree
+ * @param node
+ * @return the minimum node height
+ */
+ public static double getMinNodeHeight(RootedTree tree, Node node) {
+
+ List<Node> children = tree.getChildren(node);
+ if (children.size() == 0) return tree.getHeight(node);
+
+ double minNodeHeight = Double.MAX_VALUE;
+ for (Node child : children) {
+ double height = getMinNodeHeight(tree, child);
+ if (height < minNodeHeight) {
+ minNodeHeight = height;
+ }
+ }
+ return minNodeHeight;
+ }
+
+ public static Comparator<Node> createNodeDensityComparator(final RootedTree tree) {
+
+ return new Comparator<Node>() {
+
+ public int compare(Node node1, Node node2) {
+ return getExternalNodeCount(tree, node2) - getExternalNodeCount(tree, node1);
+ }
+
+ public boolean equals(Node node1, Node node2) {
+ return compare(node1, node2) == 0;
+ }
+ };
+ }
+
+ public static Comparator<Node> createNodeDensityMinNodeHeightComparator(final RootedTree tree) {
+
+ return new Comparator<Node>() {
+
+ public int compare(Node node1, Node node2) {
+ int larger = getExternalNodeCount(tree, node1) - getExternalNodeCount(tree, node2);
+
+ if (larger != 0) return larger;
+
+ double tipRecent = getMinNodeHeight(tree, node2) - getMinNodeHeight(tree, node1);
+ if (tipRecent > 0.0) return 1;
+ if (tipRecent < 0.0) return -1;
+ return 0;
+ }
+
+ public boolean equals(Node node1, Node node2) {
+ return compare(node1, node2) == 0;
+ }
+ };
+ }
+
+
+ /**
+ * Generates a unique representation of a node
+ *
+ * @param tree tree
+ * @param node node
+ */
+ private static String toUniqueNewick(RootedTree tree, Node node) {
+ return toUniqueNewickByAttribute(tree, node, null);
+ }
+
+ /**
+ * Generates a unique representation of a node printing only its attribute
+ *
+ * @param tree tree
+ * @param node node
+ * @param attribute when not null, use attribute to get taxa name
+ * @return tree representation
+ */
+ private static String toUniqueNewickByAttribute(RootedTree tree, Node node, String attribute) {
+ StringBuilder buffer = new StringBuilder();
+ if (tree.isExternal(node)) {
+ final Taxon taxon = tree.getTaxon(node);
+ final String name = attribute != null ? (String) taxon.getAttribute(attribute) : taxon.getName();
+ buffer.append(name);
+ if (tree.hasLengths()) {
+ buffer.append(':');
+ buffer.append(tree.getLength(node));
+ }
+ } else {
+ buffer.append('(');
+ List<Node> children = tree.getChildren(node);
+// if( children.size() == 1)
+// return toUniqueNewickByAttribute(tree,children.get(0),attribute);
+//
+
+
+ final int last = children.size() - 1;
+ // Generate a uniquely sorted list of children
+ List<String> childStrings = new ArrayList<String>();
+ for (Node aChildren : children) {
+ childStrings.add(toUniqueNewickByAttribute(tree, aChildren, attribute));
+ }
+ Collections.sort(childStrings,
+ new Comparator<String>() {
+ public int compare(String arg0, String arg1) {
+ return arg1.compareTo(arg0);
+ }
+ });
+ for (int i = 0; i <= last; i++) {
+ buffer.append(childStrings.get(i));
+ buffer.append(i == last ? ')' : ',');
+ }
+
+ final Node parent = tree.getParent(node);
+ if (parent != null && tree.hasLengths()) {
+ buffer.append(":").append(tree.getLength(node));
+ }
+ }
+ return buffer.toString();
+ }
+
+ // debug aid - print a representetion of node omitting branches
+ static public String DEBUGsubTreeRep(RootedTree tree, Node node) {
+ if (tree.isExternal(node)) {
+ return tree.getTaxon(node).getName();
+ }
+ StringBuilder b = new StringBuilder();
+ for (Node x : tree.getChildren(node)) {
+ if (b.length() > 0) b.append(",");
+ b.append(DEBUGsubTreeRep(tree, x));
+ }
+ return '(' + b.toString() + ')';
+ }
+
+
+ /**
+ * This method creates an unattached copy of the given rooted tree such that changes to the copied tree do not affect the original tree.
+ * @param treeToCopy the tree to copy
+ * @return an equivalent tree to treeToCopy (NB this may not be of the same RootedTree subclass as treeToCopy)
+ */
+ public static RootedTree copyTree(RootedTree treeToCopy){
+ return new CompactRootedTree(treeToCopy);
+ }
+
+ // debug aid - unrooted tree printout - un-comment in emergency
+
+// private static String nodeName(Tree tree, Node n) {
+// if( tree.isExternal(n) ) {
+// return tree.getTaxon(n).getName();
+// }
+// final String s = n.toString();
+// return s.substring(s.lastIndexOf('@'));
+// }
+//
+// private static void DEBUGshowTree(Tree tree) {
+// for (Node e : tree.getNodes()) {
+// final String name = nodeName(tree, e);
+// System.out.print(name + ":");
+// for( Node n : tree.getAdjacencies(e) ) {
+// try {
+// System.out.print(" {" + nodeName(tree, n) + " : " + tree.getEdgeLength(e, n) + "}");
+// } catch (Graph.NoEdgeException e1) {
+// e1.printStackTrace();
+// }
+// }
+// System.out.println();
+// }
+// }
+
+}
\ No newline at end of file
diff --git a/src/jebl/evolution/treesimulation/CoalescentIntervalGenerator.java b/src/jebl/evolution/treesimulation/CoalescentIntervalGenerator.java
new file mode 100644
index 0000000..4d68d32
--- /dev/null
+++ b/src/jebl/evolution/treesimulation/CoalescentIntervalGenerator.java
@@ -0,0 +1,208 @@
+/*
+ * CoalescentIntervalGenerator.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.treesimulation;
+
+import jebl.evolution.coalescent.DemographicFunction;
+
+/**
+ * This is a class that draws coalescent intervals under the given demographic function. If
+ * the demographic function class has an analytical function for the integral of 1/N(t) then
+ * this is used otherwise a numerical integrator is used.
+ *
+ * To generate a tree using this class, see the TreeSimulator class in jebl.evolution.trees.
+ *
+ * Much of this class was derived from C++ code provided by Oliver Pybus.
+ *
+ * @author Andrew Rambaut
+ * @author Oliver Pybus
+ * @version $Id: CoalescentIntervalGenerator.java 563 2006-12-07 17:43:10Z rambaut $
+ */
+public class CoalescentIntervalGenerator implements IntervalGenerator {
+
+ public CoalescentIntervalGenerator(DemographicFunction demographicFunction) {
+ this.demographicFunction = demographicFunction;
+ }
+
+ protected DemographicFunction demographicFunction;
+
+ private static final double LARGE_POSITIVE_NUMBER = 1.0e50;
+ private static final double LARGE_NEGATIVE_NUMBER = -1.0e50;
+ private static final double INTEGRATION_PRECISION = 1.0e-5;
+ private static final double INTEGRATION_MAX_ITERATIONS = 50;
+
+ public double getInterval(double criticalValue, int lineageCount, double currentHeight) {
+
+ assert lineageCount >= 2;
+ assert criticalValue > 0.0 && criticalValue < 1.0;
+
+ // The simulation equation cannot be rearranged for g, and is therefore solved
+ // numerically. The integration method is determined by 'numericalIntegration',
+ // the correct method is selected within SolveForIntervalSize();
+ double c = (-Math.log(criticalValue)) / ((0.5 * lineageCount * (lineageCount - 1)));
+ return solveForIntervalSize(c, currentHeight);
+ }
+
+ /**
+ * The integral of 1/N(x) between ti and gi always increases as gi increases (it never
+ * decreases or stays constant). This is because 1/N(x) is always greater than zero. We use
+ * this result to solve for gi. Let c=-ln(u)/(i choose 2). Given a value of gi, if the
+ * integral>c, then gi is greater than the solution. If the integral<c, then gi is smaller
+ * than the solution. This function finds values which bracket the solution, passes them
+ * to FindSolution(), then returns the solved interval size.
+ *
+ * @param inC
+ * @param inT time of last coalescence
+ * @return the solution
+ */
+ private double solveForIntervalSize(double inC, double inT) {
+ assert(inT >= 0);
+ assert(inC >= 0);
+
+ double constant = inC; // constant must be equal to -ln(U)/(i choose 2)
+ double lowBracket = 0.0;
+ double highBracket = 0.0;
+ double factor=1.6;
+
+ for (double gEst=1.0; gEst < LARGE_POSITIVE_NUMBER; gEst=gEst*factor) {
+
+ if (getIntegral(gEst, inT) > constant) { // solution must be smaller than gEst
+ highBracket = gEst;
+
+ if (gEst == 1.0) {
+ // solution is between 0 and 1
+ lowBracket = 0.0;
+ return findSolution(constant, lowBracket, highBracket, inT);
+ } else {
+ // solution is between gEst/FACTOR and gEst
+ lowBracket = (gEst/factor);
+ return findSolution(constant, lowBracket, highBracket, inT);
+ }
+ }
+ }
+
+ throw new RuntimeException("Unable to bracket solution in solveForIntervalSize");
+ }
+
+ /** This function returns the solved interval size. inLB and inHB must bracket the solution.
+ * The function is a straightforward bisection search which is continued until the
+ * desired accuracy is reached.
+ */
+ private double findSolution(double inConst, double lowB, double highB, double t) {
+
+ assert(t >= 0.0);
+
+ double solutionAccuracy = 1.0E-5;
+ double halfway;
+
+ do {
+ halfway = ((highB - lowB) / 2.0) + lowB;
+ if (getIntegral(halfway, t) > inConst) {
+ // solution must be smaller than halfway
+ highB = halfway;
+ } else {
+ // solution must be larger than halfway
+ lowB = halfway;
+ }
+
+ assert(highB >= lowB);
+
+ } while ((highB - lowB) > solutionAccuracy);
+
+ return lowB;
+ }
+
+ /**
+ * Returns the integral of 1/N(x) between t and g+t, calling either the getAnalyticalIntegral or
+ * getNumericalIntegral function as appropriate.
+ */
+ private double getIntegral(double g, double t) {
+
+ if (g==0.0) {
+ // integral value equals 0 if g=0
+ return 0.0;
+ }
+
+ if (demographicFunction.hasIntegral()) {
+ // Calculate integral analytically
+ return demographicFunction.getIntegral(t, t + g);
+ } else {
+ // Calculate integral numerically
+ return getNumericalIntegral(t, t + g);
+ }
+
+ }
+
+ /**
+ * Evaluates the definite integral of 1/N(x) between inLowBound and inHighBound.
+ */
+ private double getNumericalIntegral(double inLowBound, double inHighBound)
+ {
+ double lastST = LARGE_NEGATIVE_NUMBER;
+ double lastS = LARGE_NEGATIVE_NUMBER;
+
+ assert(inHighBound > inLowBound);
+
+ for (int j = 1; j <= INTEGRATION_MAX_ITERATIONS; j++) {
+ // iterate doTrapezoid() until answer obtained
+
+ double st = doTrapezoid(j, inLowBound, inHighBound, lastST);
+ double s = (4.0 * st - lastST) / 3.0;
+
+ // If answer is within desired accuracy then return
+ if (Math.abs(s - lastS) < INTEGRATION_PRECISION * Math.abs(lastS)) {
+ return s;
+ }
+ lastS = s;
+ lastST = st;
+ }
+
+ throw new RuntimeException("Too many iterations in getNumericalIntegral");
+ }
+
+ /**
+ * Performs the trapezoid rule.
+ */
+ private double doTrapezoid(int n, double low, double high, double lastS) {
+
+ double s;
+
+ if (n == 1) {
+ // On the first iteration s is reset
+ double demoLow = demographicFunction.getDemographic(low); // Value of N(x) obtained here
+ assert(demoLow > 0.0);
+
+ double demoHigh = demographicFunction.getDemographic(high);
+ assert(demoHigh > 0.0);
+
+ s = 0.5 * (high - low) * ( (1.0 / demoLow) + (1.0 / demoHigh) );
+ } else {
+ int it=1;
+ for (int j = 1; j < n - 1; j++) {
+ it *= 2;
+ }
+
+ double tnm = it; // number of points
+ double del = (high - low) / tnm; // width of spacing between points
+
+ double x = low + 0.5 * del;
+
+ double sum = 0.0;
+ for (int j = 1; j <= it; j++) {
+ double demoX = demographicFunction.getDemographic(x); // Value of N(x) obtained here
+ assert(demoX > 0.0);
+
+ sum += (1.0 / demoX);
+ x += del;
+ }
+ s = 0.5 * (lastS + (high - low) * sum / tnm); // New s uses previous s value
+ }
+
+ return s;
+ }
+}
diff --git a/src/jebl/evolution/treesimulation/IntervalGenerator.java b/src/jebl/evolution/treesimulation/IntervalGenerator.java
new file mode 100644
index 0000000..a494905
--- /dev/null
+++ b/src/jebl/evolution/treesimulation/IntervalGenerator.java
@@ -0,0 +1,18 @@
+/*
+ * IntervalGenerator.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.treesimulation;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: IntervalGenerator.java 563 2006-12-07 17:43:10Z rambaut $
+ */
+public interface IntervalGenerator {
+ double getInterval(double criticalValue, int lineageCount, double currentHeight);
+}
diff --git a/src/jebl/evolution/treesimulation/TreeSimulator.java b/src/jebl/evolution/treesimulation/TreeSimulator.java
new file mode 100644
index 0000000..ac9aaca
--- /dev/null
+++ b/src/jebl/evolution/treesimulation/TreeSimulator.java
@@ -0,0 +1,238 @@
+/*
+ * TreeSimulator.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.evolution.treesimulation;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.io.NexusExporter;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.SimpleRootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.math.Random;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * This class provides the framework for (backwards-through-time) tree simulation. Basically,
+ * this takes a set of tips (optionally at different dates) and repeatedly coalesces them together
+ * until the MRCA is reached and the tree is returned. The time intervals between nodes are provided
+ * by the IntervalGenerator and an implementation of this is the CoalescentIntervalGenerator in
+ * the jebl.evolution.coalescent package.
+ * @author Andrew Rambaut
+ * @version $Id: TreeSimulator.java 563 2006-12-07 17:43:10Z rambaut $
+ */
+public class TreeSimulator {
+
+ /**
+ * A constructor for a given number of taxa, all sampled at the same time
+ * @param intervalGenerator
+ * @param taxonCount
+ */
+ public TreeSimulator(IntervalGenerator intervalGenerator, String taxonPrefix, int taxonCount) {
+ this(intervalGenerator, taxonPrefix, new int[] { taxonCount }, new double[] { 0.0 } );
+ }
+
+ public TreeSimulator(IntervalGenerator intervalGenerator, String taxonPrefix, double[] samplingTimes) {
+ this.intervalGenerator = intervalGenerator;
+
+ List<Taxon> taxonList = new ArrayList<Taxon>();
+ for (int i = 0; i < samplingTimes.length; i++) {
+ Taxon taxon = Taxon.getTaxon(taxonPrefix + Integer.toString(i + 1) + "_" + Double.toString(samplingTimes[i]));
+ taxon.setAttribute("height", samplingTimes[i]);
+ taxonList.add(taxon);
+ }
+
+ setTaxa(taxonList, "height");
+ }
+
+ public TreeSimulator(IntervalGenerator intervalGenerator, String taxonPrefix, int[] samplingCounts, double[] samplingTimes) {
+ this.intervalGenerator = intervalGenerator;
+
+ List<Taxon> taxonList = new ArrayList<Taxon>();
+ int k =0;
+ for (int i = 0; i < samplingCounts.length; i++) {
+ for (int j = 0; j < samplingCounts[i]; j++) {
+ Taxon taxon = Taxon.getTaxon(taxonPrefix + Integer.toString(k + 1) + "_" + Double.toString(samplingTimes[i]));
+ taxon.setAttribute("height", samplingTimes[i]);
+ taxonList.add(taxon);
+ k++;
+ }
+ }
+
+ setTaxa(taxonList, "height");
+ }
+
+ /**
+ * A constructor for a given collection of taxa. If the taxa have the attribute given by heightAttributeName then
+ * this will be used, otherwise a height of 0.0 will be assumed.
+ * @param intervalGenerator
+ * @param taxa
+ */
+ public TreeSimulator(IntervalGenerator intervalGenerator, final Collection<Taxon> taxa, final String heightAttributeName) {
+ this.intervalGenerator = intervalGenerator;
+ List<Taxon> taxonList = new ArrayList<Taxon>();
+ for (Taxon taxon : taxa) {
+ taxonList.add(taxon);
+ }
+ setTaxa(taxonList, heightAttributeName);
+ }
+
+ private void setTaxa(List<Taxon> taxa, final String heightAttributeName) {
+ this.taxa = taxa;
+ this.heightAttributeName = heightAttributeName;
+ Collections.sort(this.taxa, new Comparator<Taxon>() {
+ public int compare(Taxon taxon1, Taxon taxon2) {
+ double height1 = 0.0;
+ double height2 = 0.0;
+
+ Double attr = (Double)taxon1.getAttribute(heightAttributeName);
+ if (attr != null) {
+ height1 = attr.doubleValue();
+ }
+ attr = (Double)taxon1.getAttribute(heightAttributeName);
+ if (attr != null) {
+ height2 = attr.doubleValue();
+ }
+ return Double.compare(height1, height2);
+ }
+ });
+ }
+
+ public Tree simulate() {
+ return simulate(false);
+ }
+
+ public Tree simulate(boolean medianHeights) {
+ SimpleRootedTree tree = new SimpleRootedTree();
+
+ Node[] tipNodes = new Node[taxa.size()];
+ int i = 0;
+ // create all the tips
+ for (Taxon taxon : taxa) {
+ Node tip = tree.createExternalNode(taxon);
+ tree.setHeight(tip, (Double)taxon.getAttribute(heightAttributeName));
+
+ tipNodes[i] = tip;
+
+ i++;
+ }
+
+ List<Node> activeNodes = new ArrayList<Node>();
+
+ double currentHeight = 0.0;
+ double nextHeight = 0.0;
+
+ // get at least two tips
+ int nextSampleNode = 0;
+
+ do {
+
+ // if there are less than 2 lineages available or
+ // if the new height is older than some samples,
+ // then add the samples and reset the process
+ while (nextSampleNode < tipNodes.length &&
+ (activeNodes.size() < 2 || nextHeight >= tree.getHeight(tipNodes[nextSampleNode]))) {
+
+ // Current height is now the height of the sampled node
+ currentHeight = tree.getHeight(tipNodes[nextSampleNode]);
+
+ // add the sampled node
+ activeNodes.add(tipNodes[nextSampleNode]);
+ nextSampleNode ++;
+ }
+
+ if (!medianHeights) {
+ // draw a new height
+ double U = Random.nextDouble();
+ nextHeight = currentHeight + intervalGenerator.getInterval(U, activeNodes.size(), currentHeight);
+ } else {
+ // obtain the median height
+ nextHeight = currentHeight + intervalGenerator.getInterval(0.5, activeNodes.size(), currentHeight);
+ }
+
+ if (nextSampleNode >= tipNodes.length || nextHeight < tree.getHeight(tipNodes[nextSampleNode])) {
+ // draw two nodes from the list of those available and remove them
+ Node leftNode = activeNodes.remove(Random.nextInt(activeNodes.size()));
+ Node rightNode = activeNodes.remove(Random.nextInt(activeNodes.size()));
+
+ Node node = coalesce(leftNode, rightNode, tree, nextHeight);
+ activeNodes.add(node);
+
+ currentHeight = nextHeight;
+ }
+
+ } while (nextSampleNode < tipNodes.length || activeNodes.size() > 1);
+
+ return tree;
+ }
+
+ private Node coalesce(Node leftNode, Node rightNode, SimpleRootedTree tree, double height) {
+
+ Node node = null;
+
+ node = tree.createInternalNode(Arrays.asList(new Node[] { leftNode, rightNode }));
+ tree.setHeight(node, height);
+
+ return node;
+ }
+
+ private final IntervalGenerator intervalGenerator;
+ private List<Taxon> taxa;
+ private String heightAttributeName;
+
+ /**
+ * A main() to test the tree simulation classes. In this case the interval generator is a simple
+ * anonymous class that simply returns the uniform random deviate that it is passed.
+ * @param args
+ */
+ public static void main(String[] args) {
+
+ double[] samplingTimes = new double[] {
+ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
+ };
+
+ // A simple uniform interval generator..
+ IntervalGenerator intervals = new IntervalGenerator() {
+ public double getInterval(double criticalValue, int lineageCount, double currentHeight) {
+ return criticalValue;
+ }
+ };
+ TreeSimulator sim = new TreeSimulator(intervals, "tip", samplingTimes);
+
+ int REPLICATE_COUNT = 100;
+
+ try {
+ Tree[] trees = new Tree[REPLICATE_COUNT];
+
+ System.err.println("Simulating " + REPLICATE_COUNT + " trees of " + samplingTimes.length + " tips:");
+ System.err.print("[");
+ for (int i = 0; i < REPLICATE_COUNT; i++) {
+
+ trees[i] = sim.simulate();
+ if (i != 0 && i % 100 == 0) {
+ System.err.print(".");
+ }
+ }
+ System.err.println("]");
+
+ Writer writer = new FileWriter("simulated.trees");
+ NexusExporter exporter = new NexusExporter(new OutputStreamWriter(System.out));
+
+ exporter.exportTrees(Arrays.asList(trees));
+
+ writer.close();
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/jebl/gui/trees/treecomponent/RootedTreeComponent.java b/src/jebl/gui/trees/treecomponent/RootedTreeComponent.java
new file mode 100644
index 0000000..74b5841
--- /dev/null
+++ b/src/jebl/gui/trees/treecomponent/RootedTreeComponent.java
@@ -0,0 +1,192 @@
+/*
+ * RootedTreeComponent.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.gui.trees.treecomponent;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.util.Vector;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: RootedTreeComponent.java 309 2006-05-02 08:22:06Z rambaut $
+ */
+public class RootedTreeComponent extends JComponent implements Printable {
+
+ /** the tree */
+ protected RootedTree tree = null;
+
+ /** the tree painter */
+ private RootedTreePainter treePainter = null;
+
+ public RootedTreeComponent(RootedTreePainter treePainter) {
+ this.treePainter = treePainter;
+ init();
+ }
+
+ /**
+ * @param tree the tree
+ */
+ public RootedTreeComponent(RootedTreePainter treePainter, RootedTree tree) {
+ this.treePainter = treePainter;
+
+ init();
+ setTree(tree);
+ }
+
+ /**
+ * Called by all constructors.
+ */
+ void init() {
+ enableEvents(AWTEvent.MOUSE_EVENT_MASK);
+
+ // adds a mouse listener
+ addMouseListener(new MListener());
+ }
+
+ /**
+ * Set the tree.
+ */
+ public void setTree(RootedTree tree) {
+ this.tree = tree;
+ repaint();
+ }
+
+ /**
+ * Set line style
+ */
+ public void setLineStyle(Stroke lineStroke, Paint linePaint) {
+ treePainter.setLineStyle(lineStroke, linePaint);
+ repaint();
+ }
+
+ /**
+ * Set hilight style
+ */
+ public void setHilightStyle(Stroke hilightStroke, Paint hilightPaint) {
+ treePainter.setHilightStyle(hilightStroke, hilightPaint);
+ repaint();
+ }
+
+ /**
+ * Set label style.
+ */
+ public void setLabelStyle(Font labelFont, Paint labelPaint) {
+ treePainter.setLabelStyle(labelFont, labelPaint);
+ repaint();
+ }
+
+ /**
+ * Set hilight label style.
+ */
+ public void setHilightLabelStyle(Font hilightLabelFont, Paint hilightLabelPaint) {
+ treePainter.setHilightLabelStyle(hilightLabelFont, hilightLabelPaint);
+ repaint();
+ }
+
+ public void paintComponent(Graphics g) {
+
+ if (tree == null) return;
+
+ Dimension size = getSize();
+ Graphics2D g2d = (Graphics2D)g;
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ treePainter.paintTree(g2d, size, tree);
+
+ }
+
+ //********************************************************************
+ // Printable interface
+ //********************************************************************
+
+ public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
+ if (pageIndex > 0) {
+ return(NO_SUCH_PAGE);
+ } else {
+ Graphics2D g2d = (Graphics2D)g;
+
+ double x0 = pageFormat.getImageableX();
+ double y0 = pageFormat.getImageableY();
+
+ double w0 = pageFormat.getImageableWidth();
+ double h0 = pageFormat.getImageableHeight();
+
+ double w1 = getWidth();
+ double h1 = getHeight();
+
+ double scale;
+
+ if (w0 / w1 < h0 / h1) {
+ scale = w0 / w1;
+ } else {
+ scale = h0 /h1;
+ }
+
+ g2d.translate(x0, y0);
+ g2d.scale(scale, scale);
+
+ // Turn off double buffering
+ paint(g2d);
+ // Turn double buffering back on
+ return(PAGE_EXISTS);
+ }
+ }
+
+ /**
+ * Add a plot listener
+ */
+ public void addListener(Listener listener) {
+
+ listeners.add(listener);
+ }
+
+ /**
+ * Tells tree listeners that a node has been clicked.
+ */
+ protected void fireNodeClickedEvent(Node node) {
+
+ for (int i=0; i < listeners.size(); i++) {
+ Listener listener = listeners.elementAt(i);
+ listener.nodeClicked(tree, node);
+ }
+ }
+
+ // Listeners
+
+ private Vector<Listener> listeners = new Vector<Listener>();
+
+ public interface Listener {
+
+ public void nodeClicked(RootedTree tree, Node node);
+
+ }
+
+ public class Adaptor implements Listener {
+
+ public void nodeClicked(RootedTree tree, Node node) { }
+
+ }
+
+ public class MListener extends MouseAdapter {
+
+ public void mouseClicked(MouseEvent me) {
+
+ Node node = treePainter.findNodeAtPoint(me.getPoint());
+
+ fireNodeClickedEvent(node);
+ }
+ }
+}
diff --git a/src/jebl/gui/trees/treecomponent/RootedTreePainter.java b/src/jebl/gui/trees/treecomponent/RootedTreePainter.java
new file mode 100644
index 0000000..babddad
--- /dev/null
+++ b/src/jebl/gui/trees/treecomponent/RootedTreePainter.java
@@ -0,0 +1,54 @@
+/*
+ * RootedTreePainter.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package jebl.gui.trees.treecomponent;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.Point2D;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: RootedTreePainter.java 309 2006-05-02 08:22:06Z rambaut $
+ */
+public interface RootedTreePainter {
+
+ /**
+ * Set line style
+ */
+ void setLineStyle(Stroke lineStroke, Paint linePaint);
+
+ /**
+ * Set hilight style
+ */
+ void setHilightStyle(Stroke hilightStroke, Paint hilightPaint);
+
+ /**
+ * Set label style.
+ */
+ void setLabelStyle(Font labelFont, Paint labelPaint);
+
+ /**
+ * Set hilight label style.
+ */
+ void setHilightLabelStyle(Font hilightLabelFont, Paint hilightLabelPaint);
+
+ /**
+ * Do the actual painting.
+ */
+ void paintTree(Graphics2D g, Dimension size, RootedTree tree);
+
+ /**
+ * Find the node under point. Returns -1 if not found.
+ */
+ public Node findNodeAtPoint(Point2D point);
+
+}
diff --git a/src/jebl/gui/trees/treecomponent/SquareTreePainter.java b/src/jebl/gui/trees/treecomponent/SquareTreePainter.java
new file mode 100644
index 0000000..c5cfeff
--- /dev/null
+++ b/src/jebl/gui/trees/treecomponent/SquareTreePainter.java
@@ -0,0 +1,311 @@
+/*
+ * SquareTreePainter.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.gui.trees.treecomponent;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Alexei Drummond
+ *
+ * @version $Id: SquareTreePainter.java 309 2006-05-02 08:22:06Z rambaut $
+ */
+public class SquareTreePainter implements RootedTreePainter {
+
+ protected Stroke lineStroke = new BasicStroke((float)2.0);
+ protected Paint linePaint = Color.black;
+
+ protected Stroke hilightStroke = new BasicStroke((float)2.0);
+ protected Paint hilightPaint = Color.blue;
+
+ protected int maxFontSize = 12;
+
+ protected Font labelFont = new Font("Helvetica", Font.PLAIN, 12);
+ protected Paint labelPaint = Color.black;
+
+ protected Font hilightLabelFont = new Font("Helvetica", Font.PLAIN, 12);
+ protected Paint hilightLabelPaint = Color.blue;
+
+ public SquareTreePainter() {
+ this.rememberYPositions = false;
+ }
+
+ public SquareTreePainter(boolean rememberYPositions) {
+ this.rememberYPositions = rememberYPositions;
+ }
+
+ /**
+ * Set line style
+ */
+ public void setLineStyle(Stroke lineStroke, Paint linePaint) {
+ this.lineStroke = lineStroke;
+ this.linePaint = linePaint;
+ }
+
+ /**
+ * Set line style
+ */
+ public void setLinePaint(Paint linePaint) {
+ this.linePaint = linePaint;
+ }
+
+
+ /**
+ * Set hilight style
+ */
+ public void setHilightStyle(Stroke hilightStroke, Paint hilightPaint) {
+ this.hilightStroke = hilightStroke;
+ this.hilightPaint = hilightPaint;
+ }
+
+ public void setFontSize(int size) {
+ maxFontSize = size;
+ }
+
+ /**
+ * Set label style.
+ */
+ public void setLabelStyle(Font labelFont, Paint labelPaint) {
+ this.labelFont = labelFont;
+ this.labelPaint = labelPaint;
+ }
+
+ /**
+ * Set hilight label style.
+ */
+ public void setHilightLabelStyle(Font hilightLabelFont, Paint hilightLabelPaint) {
+ this.hilightLabelFont = hilightLabelFont;
+ this.hilightLabelPaint = hilightLabelPaint;
+ }
+
+ public void setUserDefinedHeight(double height) {
+ this.userDefinedHeight = height;
+ }
+
+ public void drawLabels(boolean drawLabels) {
+ this.drawLabels = drawLabels;
+ }
+
+ public void drawHorizontals(boolean drawHorizontals) {
+ this.drawHorizontals = drawHorizontals;
+ }
+
+ public void drawVerticals(boolean drawVerticals) {
+ this.drawVerticals = drawVerticals;
+ }
+
+ /**
+ * Do the actual painting.
+ */
+ public void paintTree(Graphics2D g2, Dimension size, RootedTree tree) {
+
+ if (tree == null) return;
+
+ scaleY = ((double)size.height) / (tree.getExternalNodes().size());
+
+ double maxLabelHeight = scaleY;
+
+ int fontSize = maxFontSize + 1;
+ do {
+ fontSize --;
+ labelFont = new Font("Helvetica", Font.PLAIN, fontSize);
+ g2.setFont(labelFont);
+ } while (fontSize > 1 && g2.getFontMetrics().getAscent() > maxLabelHeight);
+
+ hilightLabelFont = new Font("Helvetica", Font.PLAIN, fontSize);
+
+ double maxLabelWidth = getMaxLabelWidth(g2, tree);
+
+ currentY = 0.5;
+
+ double treeHeight = tree.getHeight(tree.getRootNode());
+ double height;
+ if (userDefinedHeight < 0.0) {
+ height = treeHeight;
+ } else {
+ height = userDefinedHeight;
+ }
+
+ scaleX = ((double)size.width - 4 - maxLabelWidth) / (height * 1.02);
+
+ paintNode(g2, tree, tree.getRootNode(), 0.0, (height * 1.02)-treeHeight, false);
+ }
+
+ /**
+ * Paint a node.
+ */
+ private double paintNode(Graphics2D g2, RootedTree tree, Node node,
+ double x0, double x1, boolean hilight) {
+
+ double y;
+
+ double ix0 = convertX(x0);
+ double ix1 = convertX(x1);
+ double iy;
+
+ if (tree.isExternal(node)) {
+
+ if (rememberYPositions) {
+ // remember the y positions of taxa that you have seen before... AD
+ String taxonId = tree.getTaxon(node).getName();
+ Double pos = yPositionMap.get(taxonId);
+ if (pos != null) {
+ y = pos;
+ } else {
+ y = currentY;
+ currentY += 1.0;
+ yPositionMap.put(taxonId, y);
+ }
+ } else {
+ y = currentY;
+ currentY += 1.0;
+ }
+
+ if (hilight) {
+ g2.setPaint(hilightLabelPaint);
+ g2.setFont(hilightLabelFont);
+ } else {
+ g2.setPaint(labelPaint);
+ g2.setFont(labelFont);
+ }
+
+
+ String label = tree.getTaxon(node).getName();
+ double labelWidth = g2.getFontMetrics().stringWidth(label);
+ double labelHeight = g2.getFontMetrics().getAscent();
+ double labelOffset = labelHeight / 2;
+
+ iy = convertY(y);
+
+ if (label != null && label.length() > 0 && drawLabels) {
+ g2.drawString(label, (float)(ix1 + 4), (float)(iy + labelOffset));
+ }
+
+
+ nodeRectVert.put(node,new Rectangle.Double(ix1 + 4, iy, labelWidth, labelHeight));
+
+ if (hilight) {
+ g2.setPaint(hilightPaint);
+ g2.setStroke(hilightStroke);
+ } else {
+ // use tree color attribute if set
+ g2.setPaint(linePaint);
+ g2.setStroke(lineStroke);
+ }
+
+ } else {
+ double y0, y1;
+
+ List<Node> children = tree.getChildren(node);
+
+ Node child = children.get(0);
+ double length = tree.getHeight(node) - tree.getHeight(child);
+
+ y0 = paintNode(g2, tree, child, x1, x1+length, hilight);
+ y1 = y0;
+
+ for (int i = 1; i < children.size(); i++) {
+ child = children.get(i);
+ length = tree.getHeight(node) - tree.getHeight(child);
+
+ y1 = paintNode(g2, tree, child, x1, x1+length, hilight);
+ }
+
+ double iy0 = convertY(y0);
+ double iy1 = convertY(y1);
+
+ if (hilight) {
+ g2.setPaint(hilightPaint);
+ g2.setStroke(hilightStroke);
+ } else {
+ // use tree color attribute if set
+ g2.setPaint(linePaint);
+ g2.setStroke(lineStroke);
+ }
+
+ if (drawHorizontals) {
+ Line2D line = new Line2D.Double(ix1, iy0, ix1, iy1);
+ g2.draw(line);
+ }
+
+ nodeRectVert.put(node, new Rectangle.Double(ix1-2, iy0-2, 5, (iy1 - iy0) + 4));
+
+ y = (y1 + y0) / 2;
+ iy = convertY(y);
+
+ }
+
+ if (drawVerticals) {
+ Line2D line = new Line2D.Double(ix0, iy, ix1, iy);
+ g2.draw(line);
+ }
+
+ nodeRectHoriz.put(node,new Rectangle.Double(ix0-2, iy-2, (ix1 - ix0) + 4, 5));
+
+ return y;
+ }
+
+ private double convertX(double x) { return x * scaleX; }
+
+ private double convertY(double y) { return y * scaleY; }
+
+ /**
+ * @return the maximum label width
+ */
+ private double getMaxLabelWidth(Graphics2D g, RootedTree tree) {
+ double maxLabelWidth = 0.0;
+ for (Taxon taxon : tree.getTaxa()) {
+ String label = taxon.getName();
+ double labelWidth = g.getFontMetrics().stringWidth(label);
+ if (labelWidth > maxLabelWidth)
+ maxLabelWidth = labelWidth;
+ }
+ return maxLabelWidth;
+ }
+
+ /**
+ * Find the node under point. Returns null if not found.
+ */
+ public final Node findNodeAtPoint(Point2D point) {
+
+ for (Map.Entry entry : nodeRectVert.entrySet()) {
+ Node node = (Node)entry.getKey();
+ Rectangle rect = (Rectangle)entry.getValue();
+ if (rect.contains(point)) return node;
+ }
+ for (Map.Entry entry : nodeRectHoriz.entrySet()) {
+ Node node = (Node)entry.getKey();
+ Rectangle rect = (Rectangle)entry.getValue();
+ if (rect.contains(point)) return node;
+ }
+
+ return null;
+ }
+
+ // PRIVATE MEMBERS
+
+ private double scaleX, scaleY, currentY;
+
+ private Map<Node, Rectangle2D> nodeRectVert = new HashMap<Node, Rectangle2D>();
+ private Map<Node, Rectangle2D> nodeRectHoriz = new HashMap<Node, Rectangle2D>();
+
+ private Map<String, Double> yPositionMap = new HashMap<String, Double>();
+ private boolean rememberYPositions = false;
+ private double userDefinedHeight = -1.0;
+ private boolean drawLabels = true;
+ private boolean drawHorizontals = true;
+ private boolean drawVerticals = true;
+}
diff --git a/src/jebl/gui/trees/treeviewer/MultipleTreeViewer.java b/src/jebl/gui/trees/treeviewer/MultipleTreeViewer.java
new file mode 100644
index 0000000..a7185ce
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/MultipleTreeViewer.java
@@ -0,0 +1,132 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.io.NexusExporter;
+import jebl.evolution.trees.Tree;
+import org.virion.jam.controlpanels.*;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: MultipleTreeViewer.java 680 2007-03-29 04:36:58Z stevensh $
+ */
+public class MultipleTreeViewer extends TreeViewer {
+
+ private void init() {
+ getControlPalette().addControlsProvider(multipleTreeControlsProvider, true);
+ }
+
+ public MultipleTreeViewer() {
+ super();
+ init();
+ }
+
+ public MultipleTreeViewer(int CONTROL_PALETTE_ALIGNMENT, BasicControlPalette.DisplayMode mode) {
+ super(CONTROL_PALETTE_ALIGNMENT, mode);
+ init();
+ }
+
+ public MultipleTreeViewer(ControlPalette controlPalette, int controlsLocation) {
+ super(controlPalette, controlsLocation);
+ init();
+ }
+
+ public void setTree(Tree tree) {
+ this.trees = new ArrayList<Tree>();
+ trees.add(tree);
+ setCurrentTree(tree);
+ }
+
+ private int labelSize = 6;
+
+ public void setTrees(Collection<? extends Tree> trees) {
+ setTrees(trees, labelSize);
+ }
+
+ public void setTrees(Collection<? extends Tree> trees, int defaultLabelSize) {
+ this.trees = new ArrayList<Tree>(trees);
+ labelSize = defaultLabelSize;
+ super.setTree(this.trees.get(0), defaultLabelSize);
+ }
+
+ private void setCurrentTree(Tree tree) {
+ super.setTree(tree, labelSize);
+ }
+
+ private ControlsProvider multipleTreeControlsProvider = new ControlsProvider() {
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // do nothing
+ }
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ boolean useNames = false;
+ for( Tree t : trees ) {
+ Object name = t.getAttribute(NexusExporter.treeNameAttributeKey);
+ if( name != null && !NexusExporter.isGeneratedTreeName(name.toString()) ) {
+ useNames = true;
+ break;
+ }
+ }
+ if( useNames ) {
+ final List<String> names = new ArrayList<String>();
+ for( Tree t : trees ) {
+ final Object oname = t.getAttribute(NexusExporter.treeNameAttributeKey);
+ final String i = "" + (1+trees.indexOf(t)) + "/" + trees.size();
+ final String name = oname == null ? i : oname.toString() + " (" + i + ")";
+
+ names.add(name);
+ }
+ final JSpinner spinner1 = new JSpinner(new SpinnerListModel(names));
+
+ spinner1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setCurrentTree(trees.get( names.indexOf( (String)spinner1.getValue()) ) );
+ //this is here becasue the selection clears when we change trees, and we
+ //want to refresh the toolbar
+ treePane.clearSelection();
+ }
+ });
+ optionsPanel.addComponentWithLabel("Tree:", spinner1);
+ } else {
+ final JSpinner spinner1 = new JSpinner(new SpinnerNumberModel(1, 1, trees.size(), 1));
+
+ spinner1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setCurrentTree(trees.get((Integer) spinner1.getValue() - 1));
+ }
+ });
+ optionsPanel.addComponentWithLabel("Tree:", spinner1);
+ }
+
+ controls = new Controls("Current Tree", optionsPanel, true);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+ };
+
+ private List<Tree> trees = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreeDrawableElement.java b/src/jebl/gui/trees/treeviewer/TreeDrawableElement.java
new file mode 100644
index 0000000..d453aaf
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreeDrawableElement.java
@@ -0,0 +1,464 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Joseph Heled
+ * @version $Id$
+ */
+public abstract class TreeDrawableElement {
+ final protected Node node;
+
+ TreeDrawableElement(Node node) {
+ this.node = node;
+ }
+
+ public Point2D.Double getCenter() {
+ final Rectangle2D b = getBounds();
+ return new Point2D.Double( b.getX() + b.getWidth() / 2, b.getY() + b.getHeight()/2 );
+ }
+
+ public double getRadius2() {
+ final Rectangle2D b = getBounds();
+ final double width = b.getWidth();
+ final double h = b.getHeight();
+ return (width*width + h*h)/ 4.0;
+ }
+
+ public double getRadius() {
+ return Math.sqrt( getRadius2() );
+ }
+
+ public static boolean intersects(TreeDrawableElement e1, TreeDrawableElement e2) {
+ final Rectangle2D d = e1.getBounds();
+ final Rectangle2D d1 = e2.getBounds();
+ //System.out.println(e1.getDebugName() + " bounds " + e1.getBounds().toString());
+ //System.out.println(e2.getDebugName() + " bounds " + e2.getBounds().toString());
+ return d1.intersects(d) && e1.intersects(e2);
+ }
+
+ public Node getNode() {
+ return node;
+ }
+
+ boolean visible = true;
+
+ public void setVisible(boolean visible) { this.visible = visible; }
+
+ public boolean isVisible() { return visible; }
+
+ public abstract boolean intersects(TreeDrawableElement e);
+
+ public abstract Rectangle2D getBounds();
+
+ public abstract void setSize(int size, Graphics2D g2);
+
+ public abstract int getCurrentSize();
+
+ public abstract String getDebugName();
+
+ protected abstract void drawIt(Graphics2D g2);
+
+ public final void draw(Graphics2D g2, JViewport vp) {
+ boolean doit = true;
+ if( vp != null ) {
+ final Rectangle2D d = getBounds();
+// final Point viewPosition = vp.getViewPosition();
+// final Rectangle rectangle = vp.getBounds();
+// rectangle.translate(viewPosition.x, viewPosition.y);
+ doit = vp.getViewRect().intersects(d);
+ }
+ if( doit ) {
+ drawIt(g2);
+ }
+ }
+
+ abstract public int getMinSize();
+ abstract public int getMaxSize();
+
+ public abstract int getPriority();
+
+ public abstract boolean hit(Graphics2D g2, Rectangle rect);
+
+ // Size of variance for a set
+ private static class VarianceIndication {
+ double sum = 0.0;
+ double sum2 = 0.0;
+ long n = 0;
+
+ void add(double x) {
+ sum += x;
+ sum2 += x*x;
+ ++n;
+ }
+
+ double variance() {
+ final double avg = sum / n;
+ return (sum2 / n) - avg*avg;
+ }
+ }
+
+ enum ElementSortMethod {SORTO, SORTX, SORTY}
+
+ // debugging
+ static boolean expensiveAssert = false;
+ static boolean smallAsserts = false;
+ static int prints = 0;
+
+ static void setOverlappingVisiblitiy(Collection<TreeDrawableElement> elements, Graphics2D g2) {
+ final List<TreeDrawableElement> list = new ArrayList<TreeDrawableElement>(elements);
+
+ // get variance of each "axis of projection" (x y and distance-from origin) and element maximum along each
+ // axis
+ double maxRadious2 = 0.0, maxDY = 0.0, maxDX = 0.0;
+ VarianceIndication x = new VarianceIndication();
+ VarianceIndication y = new VarianceIndication();
+ VarianceIndication o = new VarianceIndication();
+
+ // establish a map in the process which saves the 3 values for each element. Used in sorting later
+ final Map<TreeDrawableElement, double[]> elementAxisValues =
+ new HashMap<TreeDrawableElement, double[]>(list.size());
+
+ for( TreeDrawableElement e : elements ) {
+ final Rectangle2D bounds = e.getBounds();
+ final double x1 = bounds.getMinX();
+ x.add(x1);
+
+ final double y1 = bounds.getMinY();
+ y.add(y1);
+
+ final double o1 = e.getCenter().distance(0, 0);
+ o.add(o1);
+
+ elementAxisValues.put(e, new double[]{o1, x1, y1});
+
+ if( bounds.getHeight() > maxDY ) {
+ maxDY = bounds.getHeight();
+ }
+
+ if( bounds.getWidth() > maxDX ) {
+ maxDX = bounds.getWidth();
+ }
+
+ final double r = e.getRadius2();
+ if( r > maxRadious2 ) {
+ maxRadious2 = r;
+ }
+ }
+
+ // use the projection which has the largest ratio of variance to max, which is the one likely to require
+ // the least number of pairwise element comparisons
+
+ final double maxRadious = Math.sqrt(maxRadious2);
+
+ final double xstd2 = x.variance() / maxDX;
+ final double ystd2 = y.variance() / maxDY;
+ final double ostd2 = o.variance() / maxRadious;
+ final ElementSortMethod s;
+
+ if( ostd2 > Math.max(xstd2, ystd2) ) {
+ s = ElementSortMethod.SORTO;
+ } else {
+ s = xstd2 > ystd2 ? ElementSortMethod.SORTX : ElementSortMethod.SORTY;
+ }
+
+ // which projection is used
+ final int which = s.ordinal();
+
+ // order must match that of enum
+ double[] limits = {2*maxRadious, maxDX, maxDY};
+
+ // distance along projection which insures two elements further apart than that can't overlap
+ double nonOverlapRadius = limits[which];
+
+ // order elements according to projection
+ Collections.sort(list, new Comparator<TreeDrawableElement>() {
+ public int compare(TreeDrawableElement o1, TreeDrawableElement o2) {
+ final double[] v1 = elementAxisValues.get(o1);
+ final double[] v2 = elementAxisValues.get(o2);
+
+ return (int)Math.signum(v1[which] - v2[which]);
+ }
+ });
+
+ // debug prints only
+ int nChecks = 0;
+
+ // build list of clashes for each element at max size
+ Map<TreeDrawableElement, List<TreeDrawableElement> >
+ conflicts = new HashMap<TreeDrawableElement, List<TreeDrawableElement>>();
+
+ // first element that need to be compared with current one. all the ones prior to it
+ // are known to be non clashing
+ int last = 0;
+ for(int k = 0; k < list.size(); ++k) {
+ final TreeDrawableElement ek = list.get(k);
+ final double ekd = elementAxisValues.get(ek)[which];
+
+ if( expensiveAssert ) {
+ for( int j = 0; j < last; ++j) {
+ if( intersects(ek, list.get(j)) ) {
+ System.out.println(k + "/" + j);
+ assert false;
+ }
+ }
+ }
+ if(prints>0) System.out.println("checking " + k + " " + ek.getDebugName() + "(" + ek.getCurrentSize() + ")");
+
+ for(int j = last; j < k; ++j) {
+ final TreeDrawableElement ej = list.get(j);
+
+ if( ekd - elementAxisValues.get(ej)[which] > nonOverlapRadius ) {
+ if( smallAsserts ) assert ! intersects(ek, ej);
+ assert last == j;
+ ++last;
+ continue;
+ }
+
+ ++nChecks;
+ if(prints>0) System.out.print(" against " + j + ej.getDebugName() + "(" + ej.getCurrentSize() + ")");
+
+ if( intersects(ek, ej) ) {
+ // add ej on ek conflict list and vice versa
+ if( ! conflicts.containsKey(ek) ) {
+ conflicts.put(ek, new ArrayList<TreeDrawableElement>());
+ }
+ conflicts.get(ek).add(ej);
+
+ if( ! conflicts.containsKey(ej) ) {
+ conflicts.put(ej, new ArrayList<TreeDrawableElement>());
+ }
+ conflicts.get(ej).add(ek);
+
+ if(prints>0) System.out.println(" - overlapps " );
+ } else {
+ if(prints>0) System.out.println(" - non overlapp");
+ }
+ }
+ }
+
+ if (prints>0)
+ System.out.println("using " + s.toString() + " did " + nChecks + " intersect checks for "
+ + list.size() +" elemens " + (nChecks*100.0) / (list.size()*(list.size()-1)/2));
+
+ if( conflicts.size() == 0 ) {
+ // lucky - all clear
+ return;
+ }
+
+ // Resize all clashing elements to smallest size. Remove elements as needed so that no clashes remain.
+ // inspect elemnents in decending priority order to remove lower priority elements first.
+
+ final Set<TreeDrawableElement> clashingElements = conflicts.keySet();
+
+ if( prints>0 ) {
+ for (TreeDrawableElement e : clashingElements) {
+ System.out.print(e.getDebugName() + " clashes:");
+ for( TreeDrawableElement c : conflicts.get(e) ) {
+ System.out.print(c.getDebugName() + ",");
+ }
+ System.out.println();
+ }
+ }
+
+ // add everything to queue
+ Comparator<? super TreeDrawableElement> comparator = new Comparator<TreeDrawableElement>() {
+ public int compare(TreeDrawableElement o1, TreeDrawableElement o2) {
+ final int dp = o2.getPriority() - o1.getPriority();
+ if( dp != 0 ) {
+ return dp;
+ }
+ // enforce arbitrary order on element with equal priority for repeatability
+
+ //return o1.hashCode() - o2.hashCode();
+ int dn = o1.getNode().hashCode() - o2.getNode().hashCode();
+
+ return dn;
+ }
+ };
+
+ PriorityQueue<TreeDrawableElement> queue =
+ new PriorityQueue<TreeDrawableElement>(clashingElements.size(), comparator);
+
+ // resize to smaller size
+ for (TreeDrawableElement e : clashingElements) {
+ e.setSize(e.getMinSize(), g2);
+ queue.add(e);
+ }
+
+ while (queue.peek() != null) {
+
+ TreeDrawableElement e = queue.poll();
+ if( ! e.isVisible() ) {
+ // clash detected earlier and element not visible, nothing more to do
+ assert !clashingElements.contains(e);
+ continue;
+ }
+
+ final List<TreeDrawableElement> conflicting = conflicts.get(e);
+ for (int nc = 0; nc < conflicting.size(); ++nc) {
+ final TreeDrawableElement ec = conflicting.get(nc);
+ if( intersects(e, ec) ) {
+ // intersects at smallest size, have to remove
+ ec.setVisible(false);
+ clashingElements.remove(ec);
+ conflicting.remove(nc);
+ --nc;
+ }
+ }
+
+ if (conflicting.size() == 0) {
+ // no clashed after removing, can simply restore normal size
+ e.setSize(e.getMaxSize(), g2);
+ clashingElements.remove(e);
+ }
+ }
+
+ // second pass. Node try to enlarge remaining elements
+ queue.clear();
+
+ for (TreeDrawableElement e : clashingElements) {
+ queue.add(e);
+ }
+
+ while (queue.peek() != null) {
+ TreeDrawableElement e = queue.poll();
+ final List<TreeDrawableElement> overlapping = conflicts.get(e);
+
+ // start with maximal size for element
+ int size = e.getMaxSize();
+ e.setSize(size, g2);
+
+ if(prints>0) System.out.println("** Start for " + e.getDebugName() + " (" + e.getCurrentSize() + ")");
+
+ // loop until finding a size where no overlaps. this must be the case
+ // for the min size
+ while( size >= e.getMinSize() ) {
+ e.setSize(size, g2);
+ int nc = 0;
+ for(; nc < overlapping.size(); ++nc) {
+ if( intersects(e, overlapping.get(nc) ) ) {
+ if(prints>0) System.out.println(e.getDebugName() + " (" + e.getCurrentSize() + ")"
+ + " overlaps " + overlapping.get(nc).getDebugName() +
+ " (" + overlapping.get(nc).getCurrentSize() + ")" );
+ break;
+ }
+ if(prints>0) System.out.println(e.getDebugName() + " (" + e.getCurrentSize() + ")"
+ + " is ok with " + overlapping.get(nc).getDebugName() +
+ " (" + overlapping.get(nc).getCurrentSize() + ")" );
+ }
+ if( nc == overlapping.size() ) {
+ // no overlaps - use this size
+ break;
+ }
+ --size;
+ }
+
+ if(smallAsserts) assert size >= e.getMinSize() : "for " + e.getDebugName() + " (" + size + " >= " + e.getMinSize();
+
+ // try to get elements with same priority to the same size if possible
+ int priority = e.getPriority();
+
+ for( TreeDrawableElement ec : overlapping ) {
+ if( ec.getPriority() == priority ) {
+ final int ecs = ec.getCurrentSize();
+ // size should only go down to accomodate others of same priority
+ if( ecs >= size ) {
+ // overlapping element is already larger
+ continue;
+ }
+
+ // save size as it is not allowed to go up
+ final int sizeMax = size;
+
+ // size for overlapping element
+ int ecSize = ecs;
+
+ if(prints>0) System.out.println("resolve conflict of " + e.getDebugName() + " with " + ec.getDebugName() + " - " + ecs);
+ if( smallAsserts ) {
+ assert !intersects(e, ec);
+ }
+
+ while( ecSize < size ) {
+ // take ec up, exit loop with no intersection
+ while( ecSize < size && ecSize < ec.getMaxSize() ) {
+ ec.setSize(ecSize + 1, g2);
+ if( ! intersects(e, ec) ) {
+ ++ecSize;
+ } else {
+ ec.setSize(ecSize, g2);
+ break;
+ }
+ }
+ if( smallAsserts ) assert ! intersects(e, ec);
+
+ // if overlapping element has still smaller size, take element size one down
+ if( ecSize < size && size > e.getMinSize() ) {
+ --size;
+ e.setSize(size, g2);
+ }
+ if( smallAsserts ) assert ! intersects(e, ec);
+ }
+
+ // now set size of element to largest possible given known overlapper size
+ while( size+1 < sizeMax ) {
+ e.setSize(size+1, g2);
+ if( intersects(e, ec) ) {
+ e.setSize(size, g2);
+ break;
+ }
+ ++size;
+ }
+
+ if( smallAsserts ) assert ! intersects(e, ec);
+
+ // restore overlapper to original size. it's true size will be known only when checked
+ // against all it's overlappers
+ ec.setSize(ecs, g2);
+ if( smallAsserts ) assert ! intersects(e, ec);
+ }
+ }
+
+ if( smallAsserts ) {
+ for (TreeDrawableElement aConflicting : overlapping) {
+ if (intersects(e, aConflicting)) {
+ System.out.println(e.getDebugName() + " " + e.getCurrentSize() + " conflicts with " +
+ aConflicting.getDebugName() + " " + aConflicting.getCurrentSize());
+ assert false;
+ }
+ }
+ }
+ }
+ ascheck(list);
+ }
+
+ private static void ascheck(List<TreeDrawableElement> list) {
+ if( expensiveAssert ) {
+ for(int k = 0; k < list.size(); ++k) {
+ TreeDrawableElement ek = list.get(k);
+ if( ek.isVisible() ) {
+ for(int j = 0; j < k; ++j) {
+ TreeDrawableElement ej = list.get(j);
+ if( ej.isVisible() ) {
+ boolean b = intersects(ek, ej);
+ if( b ) {
+ //b = intersects(ek, ej);
+ // b = intersects(ej, ek);
+ System.out.println(ek.getDebugName() + " (" + ek.getCurrentSize() + ") & "
+ + ej.getDebugName() + " (" + ej.getCurrentSize() + ")");
+ }
+ assert !b;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreeDrawableElementLabel.java b/src/jebl/gui/trees/treeviewer/TreeDrawableElementLabel.java
new file mode 100644
index 0000000..cd8f692
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreeDrawableElementLabel.java
@@ -0,0 +1,95 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * @author Joseph Heled
+ * @version $Id$
+ * <p/>
+ */
+public abstract class TreeDrawableElementLabel extends TreeDrawableElement {
+ protected Rectangle2D bounds;
+ protected AffineTransform transform;
+ private int priority;
+
+ public TreeDrawableElementLabel(Node node, Rectangle2D labelBounds, AffineTransform transform, int priority) {
+ super(node);
+ this.bounds = labelBounds;
+ this.transform = transform;
+ this.priority = priority;
+ }
+
+ public boolean intersects(TreeDrawableElement e) {
+ if( e instanceof TreeDrawableElementLabel ) {
+ TreeDrawableElementLabel l = (TreeDrawableElementLabel)e;
+ return intersects(this, l);
+ }
+ assert false;
+ return false;
+ }
+
+ public boolean hit(Graphics2D g2, Rectangle rect) {
+ return g2.hit(rect, getLableShape(), false);
+ }
+
+ public Rectangle2D getBounds() {
+ return getLableShape().getBounds();
+ }
+
+ private Shape getLableShape() {
+ return transform.createTransformedShape(bounds);
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ static boolean intersects(TreeDrawableElementLabel l1, TreeDrawableElementLabel l2) {
+ // System.out.println(l1.getDebugName() + " transform " + l1.transform.toString());
+ // System.out.println(l2.getDebugName() + " transform " + l2.transform.toString());
+
+ // todo (efficency) when lables are not rotated this is not required, as the bounds and real space taken
+ // todo by label are the same
+ final double[] l2p = l2.getPoints();
+
+ final double[] l1p = l1.getPoints();
+
+ Line2D[] ln1 = {
+ new Line2D.Double(l1p[0], l1p[1], l1p[2], l1p[3]) ,
+ new Line2D.Double(l1p[2], l1p[3], l1p[4], l1p[5]) ,
+ new Line2D.Double(l1p[4], l1p[5], l1p[6], l1p[7]) ,
+ new Line2D.Double(l1p[6], l1p[7], l1p[0], l1p[1]) };
+ Line2D[] ln2 = {
+ new Line2D.Double(l2p[0], l2p[1], l2p[2], l2p[3]) ,
+ new Line2D.Double(l2p[2], l2p[3], l2p[4], l2p[5]) ,
+ new Line2D.Double(l2p[4], l2p[5], l2p[6], l2p[7]) ,
+ new Line2D.Double(l2p[6], l2p[7], l2p[0], l2p[1]) };
+
+ for(int k = 0; k < 4; ++k) {
+ for(int j = 0; j < 4; ++j) {
+ if( ln1[k].intersectsLine(ln2[j]) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // order must follow the path
+ private double[] getPoints() {
+ double[] xy = {
+ bounds.getMinX(), bounds.getMinY(),
+ bounds.getMinX(), bounds.getMaxY(),
+ bounds.getMaxX(), bounds.getMaxY(),
+ bounds.getMaxX(), bounds.getMinY()};
+
+ double[] txy = new double[8];
+ transform.transform(xy, 0, txy, 0, 4);
+ return txy;
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreeDrawableElementNodeLabel.java b/src/jebl/gui/trees/treeviewer/TreeDrawableElementNodeLabel.java
new file mode 100644
index 0000000..4c4a4b6
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreeDrawableElementNodeLabel.java
@@ -0,0 +1,132 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+import jebl.gui.trees.treeviewer.painters.BasicLabelPainter;
+import jebl.gui.trees.treeviewer.painters.Painter;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * @author Joseph Heled
+ * @version $Id$
+ * <p/>
+ * Created by IntelliJ IDEA.
+ * User: joseph
+ * Date: 19/12/2006
+ * Time: 12:38:58
+ */
+public class TreeDrawableElementNodeLabel extends TreeDrawableElementLabel {
+
+ private Rectangle2D defaultBounds;
+ private Rectangle2D minBounds = null;
+ // when size for node lable is to be taken from another node, known to be larger
+ private Node nodeSizeReference;
+ private BasicLabelPainter painter;
+ private int defaultSize;
+ private int curSize;
+ private Painter.Justification taxonLabelJustification;
+ //private AffineTransform save;
+ // debug
+ private Tree tree;
+ String dtype;
+
+ TreeDrawableElementNodeLabel(Tree tree, Node node, Painter.Justification taxonLabelJustification,
+ Rectangle2D labelBounds, AffineTransform transform, int priority,
+ Node nodeSizeReference, BasicLabelPainter painter,
+ String dtype) {
+ super(node, labelBounds, transform, priority);
+
+ defaultBounds = labelBounds;
+ this.nodeSizeReference = nodeSizeReference != null ? nodeSizeReference : node;
+ //this.node = node;
+ this.taxonLabelJustification = taxonLabelJustification;
+ this.painter = painter;
+ curSize = defaultSize = (int)painter.getFontSize();
+
+ //save = new AffineTransform(transform);
+
+ this.tree = tree;
+ this.dtype = dtype;
+ }
+
+ public void setSize(int size, Graphics2D g2) {
+ if( curSize != size ) {
+ // System.out.println("Set size of " + getDebugName() + " to " + size);
+ Rectangle2D newBounds;
+ if( size == getMaxSize() ) {
+ newBounds = defaultBounds;
+ } else if( size == getMinSize() && minBounds != null ) {
+ newBounds = minBounds;
+ } else {
+ // set it up
+ // do it more effciently, share between all
+ float s = painter.getFontSize();
+ painter.setFontSize(size, false);
+ painter.calibrate(g2);
+ newBounds = new Rectangle2D.Double(0.0, 0.0, painter.getWidth(g2, nodeSizeReference), painter.getPreferredHeight());
+ if( size == getMinSize() ) {
+ minBounds = newBounds;
+ }
+ painter.setFontSize(s, false);
+ painter.calibrate(g2);
+ }
+
+ if(prints>1) System.out.println("before " + getDebugName() + " at " + curSize + " to " + size + " transorm " + transform);
+
+ final double dx = newBounds.getWidth() - bounds.getWidth();
+ final double dy = newBounds.getHeight() - bounds.getHeight();
+
+ bounds = newBounds;
+ if( taxonLabelJustification != Painter.Justification.CENTER ) {
+ transform.translate(taxonLabelJustification == Painter.Justification.RIGHT ? -dx : 0 , -dy/2);
+ } else {
+ transform.translate(-dx/2, -dy);
+ }
+ if(prints>1) System.out.println("change size of " + getDebugName() + " to " + size + " transorm " + transform);
+ curSize = size;
+ }
+ }
+
+ public String getDebugName() {
+ String name;
+ if( tree.isExternal(node) ) {
+ name = tree.getTaxon(node).getName();
+
+ } else {
+ name = Utils.DEBUGsubTreeRep(Utils.rootTheTree(tree), node);
+ }
+ return name + (dtype != null ? "(" + dtype + ")" : "");
+ }
+
+ public int getCurrentSize() {
+ return curSize;
+ }
+
+ protected void drawIt(Graphics2D g2) {
+ AffineTransform oldTransform = g2.getTransform();
+
+ g2.transform(transform);
+ float s = painter.getFontSize();
+ if( painter.setFontSize(curSize, false) ) {
+ painter.calibrate(g2);
+ }
+ painter.paint(g2, node, taxonLabelJustification, bounds);
+
+ if( painter.setFontSize(s, false) ) {
+ painter.calibrate(g2);
+ }
+ g2.setTransform(oldTransform);
+ }
+
+ public int getMinSize() {
+ return Math.min((int)painter.getFontMinSize(), defaultSize);
+ }
+
+ public int getMaxSize() {
+ return defaultSize;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer/TreePane.java b/src/jebl/gui/trees/treeviewer/TreePane.java
new file mode 100644
index 0000000..c9b1f47
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreePane.java
@@ -0,0 +1,1947 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.SortedRootedTree;
+import jebl.evolution.trees.TransformedRootedTree;
+import jebl.evolution.trees.Utils;
+import jebl.gui.trees.treeviewer.decorators.BranchDecorator;
+import jebl.gui.trees.treeviewer.painters.BasicLabelPainter;
+import jebl.gui.trees.treeviewer.painters.Painter;
+import jebl.gui.trees.treeviewer.painters.PainterListener;
+import jebl.gui.trees.treeviewer.treelayouts.TreeLayout;
+import jebl.gui.trees.treeviewer.treelayouts.TreeLayoutListener;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsProvider;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.*;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.util.*;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+/**
+ *
+ * @author Andrew Rambaut
+ * @version $Id: TreePane.java 736 2007-07-19 01:40:22Z stevensh $
+ */
+public class TreePane extends JComponent implements ControlsProvider, PainterListener, Printable {
+
+ public static boolean goBackwards = false;
+ public TreePane() {
+ setBackground(UIManager.getColor("TextArea.background"));
+ }
+
+ public RootedTree getTree() {
+ return tree;
+ }
+
+ public void setTree(RootedTree tree, Collection<Node> selectedNodes) {
+ this.originalTree = tree;
+ if (!originalTree.hasLengths()) {
+ transformBranches = true;
+ }
+
+ Painter<?>[] pl = { taxonLabelPainter, nodeLabelPainter, branchLabelPainter };
+ for( Painter<?> p : pl ) {
+ if( p instanceof BasicLabelPainter ) {
+ ((BasicLabelPainter)p).setTree(tree);
+ }
+ }
+
+
+ selectedTaxa.clear();
+ if( selectedNodes != this.selectedNodes ) {
+ this.selectedNodes.clear();
+ }
+ if( selectedNodes != null ) {
+ this.selectedNodes.addAll(selectedNodes);
+ }
+
+ setupTree();
+ }
+
+ private void setupTree() {
+ tree = originalTree;
+
+ if (orderBranches) {
+ tree = new SortedRootedTree(tree, branchOrdering);
+ }
+
+ if (transformBranches || !this.tree.hasLengths()) {
+ tree = new TransformedRootedTree(tree, branchTransform);
+ }
+
+ nodesInOrder = Utils.getNodes(tree, tree.getRootNode());
+ treeLayout.setTree(tree);
+
+ calibrated = false;
+ invalidate();
+ repaint();
+ }
+
+ public void setTreeLayout(TreeLayout treeLayout) {
+
+ this.treeLayout = treeLayout;
+ treeLayout.setTree(tree);
+ treeLayout.addTreeLayoutListener(new TreeLayoutListener() {
+ public void treeLayoutChanged() {
+ calibrated = false;
+ repaint();
+ }
+ });
+ if (controlPalette != null) controlPalette.fireControlsChanged();
+ calibrated = false;
+ invalidate();
+ repaint();
+ }
+//
+// public Rectangle2D getTreeBounds() {
+// return treeBounds;
+// }
+
+ /**
+ * This returns the scaling factor between the graphical image and the branch
+ * lengths of the tree
+ *
+ * @return the tree scale
+ */
+ public double getTreeScale() {
+ return treeScale;
+ }
+
+ public void painterChanged() {
+ calibrated = false;
+ repaint();
+ }
+
+ public void setBranchOrdering(boolean orderBranches, SortedRootedTree.BranchOrdering branchOrdering) {
+ if( this.orderBranches != orderBranches || this.branchOrdering != branchOrdering ) {
+ this.orderBranches = orderBranches;
+ this.branchOrdering = branchOrdering;
+ setupTree();
+ PREFS.getBoolean(orderBranchesPREFSkey, orderBranches);
+ }
+ }
+
+ public void setBranchTransform(boolean transformBranches, TransformedRootedTree.Transform branchTransform) {
+ if( transformBranches != this.transformBranches || branchTransform != this.branchTransform ) {
+ this.transformBranches = transformBranches;
+ this.branchTransform = branchTransform;
+ setupTree();
+ PREFS.putBoolean(transformBanchesPREFSkey, transformBranches);
+ }
+ }
+
+ public boolean isShowingRootBranch() {
+ return showingRootBranch;
+ }
+
+ public void setShowingRootBranch(boolean showingRootBranch) {
+ if( this.showingRootBranch != showingRootBranch ) {
+ this.showingRootBranch = showingRootBranch;
+ calibrated = false;
+ repaint();
+ PREFS.putBoolean(showRootPREFSkey, showingRootBranch);
+ }
+ }
+
+ public void setAutoExpansion(final boolean auto) {
+ this.autoExpantion = auto;
+ setTreeAttributesForAutoExpansion();
+ //calibrated = false;
+ repaint();
+ PREFS.putBoolean(autoExPREFSkey, auto);
+ }
+
+ public boolean isShowingTaxonCallouts() {
+ return showingTaxonCallouts;
+ }
+
+ public void setShowingTaxonCallouts(boolean showingTaxonCallouts) {
+ this.showingTaxonCallouts = showingTaxonCallouts;
+ calibrated = false;
+ repaint();
+ }
+
+ public void setSelectedNode(Node selectedNode) {
+ selectedNodes.clear();
+ selectedTaxa.clear();
+ addSelectedNode(selectedNode);
+ }
+
+ public void setSelectedTaxon(Taxon selectedTaxon) {
+ selectedNodes.clear();
+ selectedTaxa.clear();
+ addSelectedTaxon(selectedTaxon);
+ }
+
+ public void setSelectedClade(Node[] selectedNode) {
+ selectedNodes.clear();
+ selectedTaxa.clear();
+ addSelectedClade(selectedNode, true);
+ }
+
+ public void setSelectedTaxa(Node selectedNode) {
+ selectedNodes.clear();
+ selectedTaxa.clear();
+ addSelectedTaxa(selectedNode);
+ }
+
+ private boolean canSelectNode(final Node selectedNode) {
+ return selectedNode != null && isNodeVisible(selectedNode);
+ }
+
+ public void addSelectedNode(Node selectedNode, boolean add) {
+ if ( canSelectNode(selectedNode) ) {
+ if( add ) {
+ selectedNodes.add(selectedNode);
+ } else {
+ selectedNodes.remove(selectedNode);
+ }
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void addSelectedNode(Node selectedNode) {
+ addSelectedNode(selectedNode, true);
+ }
+
+ public void addSelectedTaxon(Taxon selectedTaxon) {
+ if (selectedTaxon != null) {
+ selectedTaxa.add(selectedTaxon);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ /**
+ *
+ * @param selectedNode
+ * @param add true to add, false to remove existing selection
+ */
+ public void addSelectedClade(Node[] selectedNode, boolean add) {
+ if ( canSelectNode(selectedNode[0]) ) {
+ addSelectedChildClades(selectedNode, add);
+ }
+
+ if( viewSubtree ) calibrated = false;
+
+ fireSelectionChanged();
+ repaint();
+ }
+
+ private void addSelectedChildClades(Node[] selectedNode, boolean add) {
+ if( selectedNode[1] == null ) {
+ addSelectedChildClades(selectedNode[0], null, add);
+ } else {
+ addSelectedChildClades(tree.getRootNode(), selectedNode[0], add);
+ }
+ }
+
+ private void addSelectedChildClades(Node selectedNode, Node exclude, boolean add) {
+ if( selectedNode == exclude ) return;
+
+ if( add ) {
+ selectedNodes.add(selectedNode);
+ } else {
+ selectedNodes.remove(selectedNode);
+ }
+
+ for (Node child : tree.getChildren(selectedNode)) {
+ addSelectedChildClades(child, exclude, add);
+ }
+ }
+
+ public void addSelectedTaxa(Node selectedNode) {
+ if (selectedNode != null) {
+ addSelectedChildTaxa(selectedNode);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ private void addSelectedChildTaxa(Node selectedNode) {
+ if (tree.isExternal(selectedNode)) {
+ selectedTaxa.add(tree.getTaxon(selectedNode));
+ }
+ for (Node child : tree.getChildren(selectedNode)) {
+ addSelectedChildTaxa(child);
+ }
+ }
+
+ public void clearSelection() {
+ selectedNodes.clear();
+ selectedTaxa.clear();
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void annotateSelectedNodes(String name, Object value) {
+ for (Node selectedNode : selectedNodes) {
+ selectedNode.setAttribute(name, value);
+ }
+ repaint();
+ }
+
+ public void annotateSelectedTaxa(String name, Object value) {
+ for (Taxon selectedTaxon : selectedTaxa) {
+ selectedTaxon.setAttribute(name, value);
+ }
+ repaint();
+ }
+
+ final private String clpsdName = "&collapsed";
+ final private String visibleAttributeName = "&visible";
+
+ private boolean isNodeVisible(Node node) {
+ return node.getAttribute(visibleAttributeName) == null;
+ }
+
+ private boolean isNodeCollapsed(Node node) {
+ return node.getAttribute(clpsdName) != null;
+ }
+
+ private void setCladeVisisblty(final Node node, final boolean visible) {
+ for( final Node child : tree.getChildren(node) ) {
+ if( visible ) {
+ child.removeAttribute(visibleAttributeName);
+ } else {
+ child.setAttribute(visibleAttributeName, Boolean.TRUE);
+ }
+ // leave collapsed subtress alone
+
+ if( ! isNodeCollapsed(child) ) {
+ setCladeVisisblty(child, visible);
+ }
+ }
+ }
+
+
+ private void expandContract(final Node selectedNode) {
+ assert selectedNode != null;
+
+ // no point for non internal nodes
+ if( tree.isExternal(selectedNode) ) return;
+
+ final boolean wasCollapsed = selectedNode.getAttribute(clpsdName) != null;
+ if( wasCollapsed ) {
+ selectedNode.removeAttribute(clpsdName);
+ }
+ // node should not be in collapsed mode when calling this
+ setCladeVisisblty(selectedNode, wasCollapsed);
+ if( !wasCollapsed ) {
+ selectedNode.setAttribute(clpsdName, Boolean.TRUE);
+ }
+ //fireSelectionChanged();
+ calibrated = false; // labels visibility may change
+ repaint();
+ }
+
+ void toggleExpandContract(final Node selectedNode) {
+ if( canSelectNode(selectedNode) ) {
+ autoExpantion = false;
+ autoEx.setSelected(false);
+ expandContract(selectedNode);
+ }
+ }
+
+ private void setTreeAttributesForAutoExpansion() {
+ for( Node node : nodesInOrder ) {
+ node.removeAttribute(clpsdName);
+ node.removeAttribute(visibleAttributeName);
+ }
+
+ if( autoExpantion ) {
+ Set<Node> ignore = new HashSet<Node>();
+ for( Node node : nodesInOrder ) {
+ if( !ignore.contains(node) && node.getAttribute(clpsdName + "-auto") != null ) {
+ expandContract(node); // todo problem (repints)
+ ignore.addAll(Utils.getNodes(tree, node));
+ }
+ }
+ }
+ }
+
+ /**
+ * Return whether the two axis scales should be maintained
+ * relative to each other
+ *
+ * @return a boolean
+ */
+ public boolean maintainAspectRatio() {
+ return treeLayout.maintainAspectRatio();
+ }
+
+ public void setTaxonLabelPainter(Painter<Node> taxonLabelPainter) {
+ if (this.taxonLabelPainter != null) {
+ this.taxonLabelPainter.removePainterListener(this);
+ }
+ this.taxonLabelPainter = taxonLabelPainter;
+ if (this.taxonLabelPainter != null) {
+ this.taxonLabelPainter.addPainterListener(this);
+ }
+ controlPalette.fireControlsChanged();
+ calibrated = false;
+ repaint();
+ }
+
+ public Painter<Node> getTaxonLabelPainter() {
+ return taxonLabelPainter;
+ }
+
+ public void setNodeLabelPainter(Painter<Node> nodeLabelPainter) {
+ if (this.nodeLabelPainter != null) {
+ this.nodeLabelPainter.removePainterListener(this);
+ }
+ this.nodeLabelPainter = nodeLabelPainter;
+ if (this.nodeLabelPainter != null) {
+ this.nodeLabelPainter.addPainterListener(this);
+ }
+ controlPalette.fireControlsChanged();
+ calibrated = false;
+ repaint();
+ }
+
+ public Painter<Node> getNodeLabelPainter() {
+ return nodeLabelPainter;
+ }
+
+ public void setBranchLabelPainter(Painter<Node> branchLabelPainter) {
+ if (this.branchLabelPainter != null) {
+ this.branchLabelPainter.removePainterListener(this);
+ }
+ this.branchLabelPainter = branchLabelPainter;
+ if (this.branchLabelPainter != null) {
+ this.branchLabelPainter.addPainterListener(this);
+ }
+ controlPalette.fireControlsChanged();
+ calibrated = false;
+ repaint();
+ }
+
+ public Painter<Node> getBranchLabelPainter() {
+ return branchLabelPainter;
+ }
+
+ public void setScaleBarPainter(Painter<TreePane> scaleBarPainter) {
+ if (this.scaleBarPainter != null) {
+ this.scaleBarPainter.removePainterListener(this);
+ }
+ this.scaleBarPainter = scaleBarPainter;
+ if (this.scaleBarPainter != null) {
+ this.scaleBarPainter.addPainterListener(this);
+ }
+ controlPalette.fireControlsChanged();
+ calibrated = false;
+ repaint();
+ }
+
+ public Painter<TreePane> getScaleBarPainter() {
+ return scaleBarPainter;
+ }
+
+ public void setBranchDecorator(BranchDecorator branchDecorator) {
+ this.branchDecorator = branchDecorator;
+ calibrated = false;
+ repaint();
+ }
+
+ private boolean setBranchLineWeightValues(float weight) {
+ if( ((BasicStroke)branchLineStroke).getLineWidth() != weight ) {
+ branchLineStroke = new BasicStroke(weight);
+ selectionStroke = new BasicStroke(Math.max(weight + 4.0F, weight * 1.5F), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ PREFS.putFloat(branchWeightPREFSkey, weight);
+ return true;
+ }
+ return false;
+ }
+
+ public void setBranchLineWeight(float weight) {
+ if( setBranchLineWeightValues(weight) ) {
+ repaint();
+ }
+ }
+
+ public void setPreferredSize(Dimension dimension) {
+ if (treeLayout.maintainAspectRatio()) {
+ super.setPreferredSize(new Dimension(dimension.width, dimension.height));
+ } else {
+ super.setPreferredSize(dimension);
+ }
+
+ calibrated = false;
+ }
+
+ public double getHeightAt(Graphics2D graphics2D, Point2D point) {
+ try {
+ point = transform.inverseTransform(point, null);
+ } catch (NoninvertibleTransformException e) {
+ e.printStackTrace();
+ }
+ return treeLayout.getHeightOfPoint(point);
+ }
+
+
+ final private int circDiameter = 9;
+
+ //this method is for figuring out which node the user clicked on (if any)
+ // result[0] is the selected node
+ // result[1] is the parent if tree is unrooted and selection is of the clade *away* from the currect
+ // direction, null otherwise
+ Node[] getNodeAt(final Point point) {
+ final Graphics2D g2 = (Graphics2D)getGraphics();
+ Node[] result = new Node[2];
+
+ Rectangle rect = new Rectangle(point.x - 1, point.y - 1, 3, 3);
+
+ //todo: this allows clicking on node/branch labels to select nodes. At this point this behaviour is considered undesirable
+ /*for( TreeDrawableElement e : treeElements ) {
+ Node node = e.getNode();
+ if( node != null ) {
+ if( e.hit(g2, rect) ) {
+ result[0] = node;
+ return result;
+ }
+ }
+ }*/
+
+ // this piece of code must run in reverse of how the nodes are drawn so that if the user clicks on
+ // overlapping nodes, the top node is selected
+ Node rootNode = tree.getRootNode();
+ if( ! hideNode(rootNode) && checkNodeIntersects(rootNode, point)) {
+ result[0] = rootNode;
+ return result;
+ }
+ List<Node> nodesInOrder = Utils.getNodes(tree, tree.getRootNode());
+ for (int i = 0; i < nodesInOrder.size(); i++) {
+ if( !isNodeVisible(nodesInOrder.get(i)) ) continue;
+ if( hideNode(nodesInOrder.get(i)) ) continue;
+ if( tree.isExternal(nodesInOrder.get(i)) ) continue;
+ if( checkNodeIntersects(nodesInOrder.get(i), point)) {
+ result[0] = nodesInOrder.get(i);
+ return result;
+ }
+ }
+ Node[] externalNodes = tree.getExternalNodes().toArray(new Node[0]);
+ for(int i = externalNodes.length-1; i >= 0; i--){
+ if( !isNodeVisible(externalNodes[i]) ) continue;
+ if( hideNode(externalNodes[i]) ) continue;
+ if( checkNodeIntersects(externalNodes[i], point)) {
+ result[0] = externalNodes[i];
+ return result;
+ }
+ }
+ return result;
+ }
+
+ private boolean checkNodeIntersects(Node node, Point point){
+ final Point2D.Double coord = nodeCoord(node);
+ final double v = coord.distanceSq(point);
+ return v < circDiameter * circDiameter;
+ }
+
+ /**
+ * This is used for calculating which nodes are selected by dragging
+ * (with a selection rectangle)
+ * @param g2
+ * @param rect
+ * @return
+ */
+ Set<Node> getNodesAt(Graphics2D g2, Rectangle rect) {
+
+ Set<Node> nodes = new HashSet<Node>();
+
+ //todo: this allows clicking on node/branch labels to select nodes. At this point this behaviour is considered undesirable
+ /*for( TreeDrawableElement e : treeElements ) {
+ Node node = e.getNode();
+ if( node != null ) {
+ if( e.hit(g2, rect) ) {
+ nodes.add(node);
+ }
+ }
+ }*/
+
+// for (Node node : tree.getExternalNodes()) {
+// // incorrect - some lables may have been reduced in size
+// // need to get rid of taxonLabelBounds, ormake sure it is correct
+// Shape taxonLabelBound = taxonLabelBounds.get(tree.getTaxon(node));
+// if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
+// nodes.add(node);
+// }
+// }
+
+ Node[] allNodes = tree.getNodes().toArray(new Node[0]);
+ for(int i=allNodes.length-1; i >= 0; i--){
+ if(rect.contains(transform.transform(treeLayout.getNodePoint(allNodes[i]),null))){
+ nodes.add(allNodes[i]);
+ }
+ }
+
+ return nodes;
+ }
+
+ public Set<Node> getSelectedNodes() {
+ return selectedNodes;
+ }
+
+ public Set<Taxon> getSelectedTaxa() {
+ return selectedTaxa;
+ }
+
+ public Rectangle2D getDragRectangle() {
+ return dragRectangle;
+ }
+
+ public void setDragRectangle(Rectangle2D dragRectangle) {
+ this.dragRectangle = dragRectangle;
+ repaint();
+ }
+
+ public void setRuler(double rulerHeight) {
+ this.rulerHeight = rulerHeight;
+ }
+
+ public void scrollPointToVisible(Point point) {
+ scrollRectToVisible(new Rectangle(point.x, point.y, 0, 0));
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ this.controlPalette = controlPalette;
+ }
+
+ private ControlPalette controlPalette = null;
+
+ private JCheckBox autoEx;
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ controlsList.addAll(treeLayout.getControls(detachPrimaryCheckbox));
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ transformCheck = new JCheckBox("Transform branches");
+ optionsPanel.addComponent(transformCheck);
+
+ transformBranches = PREFS.getBoolean(transformBanchesPREFSkey, transformBranches);
+
+ transformCheck.setSelected(transformBranches);
+ if (!originalTree.hasLengths()) {
+ transformCheck.setEnabled(false);
+ }
+
+ final JComboBox combo1 = new JComboBox(TransformedRootedTree.Transform.values());
+ combo1.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ PREFS.putInt(branchTransformTypePREFSkey, combo1.getSelectedIndex());
+ setBranchTransform(true, (TransformedRootedTree.Transform) combo1.getSelectedItem());
+ }
+ });
+ combo1.setSelectedIndex(PREFS.getInt(branchTransformTypePREFSkey, 0));
+ branchTransform = (TransformedRootedTree.Transform) combo1.getSelectedItem();
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Transform:", combo1);
+ label1.setEnabled(transformCheck.isSelected());
+ combo1.setEnabled(transformCheck.isSelected());
+
+ transformCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean selected = transformCheck.isSelected();
+ // only on a real change
+ label1.setEnabled(selected);
+ combo1.setEnabled(selected);
+
+ setBranchTransform(selected, (TransformedRootedTree.Transform) combo1.getSelectedItem());
+ }
+ });
+
+ final JCheckBox checkBox2 = new JCheckBox("Order branches");
+ optionsPanel.addComponent(checkBox2);
+
+ orderBranches = PREFS.getBoolean(orderBranchesPREFSkey, orderBranches);
+ checkBox2.setSelected(orderBranches);
+
+ final JComboBox combo2 = new JComboBox(SortedRootedTree.BranchOrdering.values());
+ combo2.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ if(orderBranches){
+ setBranchOrdering(true, (SortedRootedTree.BranchOrdering) combo2.getSelectedItem());
+ PREFS.putInt(branchOrderingPREFSkey,combo2.getSelectedIndex());
+ }
+ }
+ });
+ combo2.setSelectedIndex(PREFS.getInt(branchOrderingPREFSkey,0));
+
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Ordering:", combo2);
+ label2.setEnabled(checkBox2.isSelected());
+ combo2.setEnabled(checkBox2.isSelected());
+
+ checkBox2.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ label2.setEnabled(checkBox2.isSelected());
+ combo2.setEnabled(checkBox2.isSelected());
+
+ setBranchOrdering(checkBox2.isSelected(),
+ (SortedRootedTree.BranchOrdering) combo2.getSelectedItem());
+ PREFS.putBoolean(orderBranchesPREFSkey, orderBranches);
+ }
+ });
+
+ if( ! tree.conceptuallyUnrooted() ) {
+ final JCheckBox checkBox3 = new JCheckBox("Show Root Branch");
+ optionsPanel.addComponent(checkBox3);
+
+ showingRootBranch = PREFS.getBoolean(showRootPREFSkey, isShowingRootBranch());
+ checkBox3.setSelected(showingRootBranch);
+ checkBox3.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setShowingRootBranch(checkBox3.isSelected());
+ }
+ });
+ } else {
+ // no root for unrooted
+ showingRootBranch = false;
+ }
+
+ final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1.0, 0.01, 48.0, 1.0));
+
+ final float weight = PREFS.getFloat(branchWeightPREFSkey, 1.0F);
+ setBranchLineWeightValues(weight);
+ spinner.setValue(weight);
+
+ spinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setBranchLineWeight(((Double) spinner.getValue()).floatValue());
+ }
+ });
+ optionsPanel.addComponentWithLabel("Line Weight:", spinner);
+
+ autoEx = new JCheckBox("Auto subtree contract");
+ autoEx.setToolTipText("Automatically contract subtrees when there is not enough space on-screen");
+ optionsPanel.addComponent(autoEx);
+
+ autoExpantion = PREFS.getBoolean(autoExPREFSkey, false);
+ autoEx.setSelected(autoExpantion);
+ autoEx.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ // we don't need to do this if we hover, right :)
+ final boolean b = autoEx.isSelected();
+ if( b != autoExpantion ) {
+ setAutoExpansion(b);
+ }
+ }
+ });
+
+ final JCheckBox subTreeShowJB = new JCheckBox("Show selected subtree only");
+ subTreeShowJB.setToolTipText("Only the selected part of the tree is shown");
+ viewSubtree = PREFS.getBoolean(viewSubtreePREFSkey, false);
+ subTreeShowJB.setSelected(viewSubtree);
+ subTreeShowJB.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean b = subTreeShowJB.isSelected();
+ PREFS.putBoolean(viewSubtreePREFSkey, subTreeShowJB.isSelected());
+ if( viewSubtree != b ) {
+ viewSubtree = b;
+ calibrated = false;
+ repaint();
+ }
+ }
+ });
+
+ optionsPanel.addComponent(subTreeShowJB);
+
+ controls = new Controls("Formatting", optionsPanel, true);
+ }
+ controlsList.add(controls);
+
+ if (getTaxonLabelPainter() != null) {
+ controlsList.addAll(getTaxonLabelPainter().getControls(detachPrimaryCheckbox));
+ }
+
+ if (getNodeLabelPainter() != null) {
+ controlsList.addAll(getNodeLabelPainter().getControls(detachPrimaryCheckbox));
+ }
+
+ if (getBranchLabelPainter() != null) {
+ controlsList.addAll(getBranchLabelPainter().getControls(detachPrimaryCheckbox));
+ }
+
+ if (getScaleBarPainter() != null) {
+ controlsList.addAll(getScaleBarPainter().getControls(detachPrimaryCheckbox));
+ }
+
+ setupTree();
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ transformCheck.setSelected((Boolean) settings.getSetting("Transformed"));
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ settings.putSetting("Transformed", transformCheck.isSelected());
+ }
+
+ private JCheckBox transformCheck;
+
+ private Controls controls = null;
+
+ private final Set<TreeSelectionListener> treeSelectionListeners = new HashSet<TreeSelectionListener>();
+
+ public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treeSelectionListeners.add(treeSelectionListener);
+ }
+
+ public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treeSelectionListeners.remove(treeSelectionListener);
+ }
+
+ private void fireSelectionChanged() {
+ for (TreeSelectionListener treeSelectionListener : treeSelectionListeners) {
+ treeSelectionListener.selectionChanged();
+ }
+ }
+
+ public void paint(Graphics graphics) {
+ if (tree == null) return;
+
+ final Graphics2D g2 = (Graphics2D) graphics;
+ if (!calibrated) calibrate(g2, getWidth(), getHeight());
+
+ final Paint oldPaint = g2.getPaint();
+ final Stroke oldStroke = g2.getStroke();
+
+ // todo disable since drawTree clears it anyway now
+// if( false ) {
+// for (Node selectedNode : selectedNodes) {
+// Shape branchPath = transform.createTransformedShape(treeLayout.getBranchPath(selectedNode));
+// if (branchPath == null) continue;
+// g2.setPaint(selectionPaint);
+// g2.setStroke(selectionStroke);
+// g2.draw(branchPath);
+// }
+//
+// for (Taxon selectedTaxon : selectedTaxa) {
+// g2.setPaint(selectionPaint);
+// Shape labelBounds = taxonLabelBounds.get(selectedTaxon);
+// if (labelBounds != null) {
+// g2.fill(labelBounds);
+// }
+// }
+// }
+
+ long start = System.currentTimeMillis();
+ drawTree(g2, true, true, getWidth(), getHeight());
+ System.err.println("tree draw " + (System.currentTimeMillis() - start) + "ms");
+
+ if (dragRectangle != null) {
+ g2.setPaint(new Color(128, 128, 128, 128));
+ g2.fill(dragRectangle);
+
+ g2.setStroke(new BasicStroke(2.0F));
+ g2.setPaint(new Color(255, 255, 255, 128));
+ g2.draw(dragRectangle);
+
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+ }
+ }
+
+ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
+
+ if (tree == null || pageIndex > 0) return NO_SUCH_PAGE;
+
+ Graphics2D g2 = (Graphics2D) graphics;
+ g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+
+ calibrated = false;
+ setDoubleBuffered(false);
+
+ drawTree(g2, false, false, pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
+
+ setDoubleBuffered(true);
+ calibrated = false;
+
+ return PAGE_EXISTS;
+ }
+
+ private Point2D.Double nodeCoord(final Node node) {
+ final Point2D nodePoint = treeLayout.getNodePoint(node);
+ final Point2D.Double result = new Point2D.Double();
+ transform.transform(nodePoint, result);
+ return result;
+ }
+
+ private void nodeMarker(Graphics2D g2, Node node) {
+ final Point2D.Double nodeLocation = nodeCoord(node);
+ final boolean isSelected = selectedNodes.contains(node);
+ final Paint color = g2.getPaint();
+
+ if( isNodeCollapsed(node) ) {
+ // final Color c = isSelected ? selectionPaint : branch;
+ // g2.setColor(c);
+ if( isSelected ) g2.setPaint(selectionPaint);
+ final Shape cn = treeLayout.getCollapsedNode(node, .25);
+ final Shape transformedShape = transform.createTransformedShape(cn);
+
+ final Stroke save = g2.getStroke();
+ g2.setStroke(collapsedStroke);
+ g2.draw(transformedShape);
+ g2.setStroke(save);
+
+// if( false) {
+// Line2D labelPath = treeLayout.getBranchLabelPath(node);
+// if( labelPath == null ) {
+// // root only?
+// labelPath = new Line2D.Double(0,0, 1,0);
+// }
+// final Point2D d1 = labelPath.getP2();
+// final Point2D d2 = labelPath.getP1();
+// final double dx = d1.getX() - d2.getX();
+// final double dy = d1.getY() - d2.getY();
+// final double branchLength = Math.sqrt(dx*dx + dy*dy);
+//
+// final double sint = dy / branchLength;
+// final double cost = dx / branchLength;
+//
+// final int r = circRadius;
+// final int h = 172*r/200;
+// int[] xp = {0, h, h};
+// int[] yp = {0, r/2, -r/2};
+//
+// for(int k = 0; k < 3; ++k) {
+// final double rx = x + xp[k] * cost - yp[k] * sint;
+// final double ry = y + xp[k] * sint + yp[k] * cost;
+// xp[k] = (int)(rx + 0.5);
+// yp[k] = (int)(ry + 0.5);
+// }
+// g2.drawPolygon(xp, yp, 3);
+// }
+ }
+
+ final Paint c = isSelected ? selectionPaint : Color.LIGHT_GRAY;
+ g2.setPaint(c);
+
+ final int rlimit = treeLayout.getNodeMarkerRadiusUpperLimit(node, transform);
+
+ final double x = nodeLocation.getX();
+ final int ix1 = (int) Math.round(x);
+ final double y = nodeLocation.getY();
+ final int iy1 = (int) Math.round(y);
+
+ int d = circDiameter;
+ if( rlimit >= 0 ) {
+ d = Math.min(2*rlimit+1, d);
+ }
+
+ final int r = (d-1)/2;
+
+ g2.fillOval(ix1 - r, iy1 - r, d, d);
+ g2.setColor(Color.black);
+ g2.drawOval(ix1 - r, iy1 - r, d, d);
+ g2.setPaint(color);
+ }
+
+ boolean viewSubtree;
+
+ private boolean hideNode(Node node) {
+ return viewSubtree && selectedNodes.size() > 0 && !selectedNodes.contains(node);
+ }
+
+ boolean preElementDrawCode = false;
+
+ public void drawTree(Graphics2D g2, boolean drawNodes, boolean clipOfscreenShapes, double width, double height) {
+
+ // this is a problem since paint draws some stuff before which print does not
+ g2.setColor(Color.WHITE);
+ g2.fillRect(0, 0, (int)width, (int)height);
+
+ final RenderingHints rhints = g2.getRenderingHints();
+ final boolean antialiasOn = rhints.containsValue(RenderingHints.VALUE_ANTIALIAS_ON);
+ if( ! antialiasOn ) {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ if (!calibrated) calibrate(g2, width, height);
+
+ // save graphics state which draw changes so that upon exit it can be restored
+
+ final AffineTransform oldTransform = g2.getTransform();
+ final Paint oldPaint = g2.getPaint();
+ final Stroke oldStroke = g2.getStroke();
+ final Font oldFont = g2.getFont();
+
+ final Set<Node> externalNodes = tree.getExternalNodes();
+ final boolean showingTaxonLables = taxonLabelPainter != null && taxonLabelPainter.isVisible();
+
+ final boolean alignedTaxa = treeLayout.alignTaxa();
+
+ for (Node node : externalNodes) {
+ if( !isNodeVisible(node) ) continue;
+ if( hideNode(node) ) continue;
+
+ final Shape branchPath = transform.createTransformedShape(treeLayout.getBranchPath(node));
+
+ if (showingTaxonCallouts && showingTaxonLables) {
+ final Shape calloutPath = transform.createTransformedShape(treeLayout.getCalloutPath(node));
+ if (calloutPath != null) {
+ g2.setStroke(taxonCalloutStroke);
+ g2.draw(calloutPath);
+ }
+ }
+
+ final Paint paint = (branchDecorator != null) ? branchDecorator.getBranchPaint(tree, node) : Color.BLACK;
+ g2.setPaint(paint);
+
+ g2.setStroke(branchLineStroke);
+ g2.draw(branchPath);
+
+ if(drawNodes)
+ nodeMarker(g2, node);
+
+ if( preElementDrawCode ) {
+ if (showingTaxonLables) {
+ final Taxon taxon = tree.getTaxon(node);
+ if( ! alignedTaxa ) {
+ taxonLabelPainter.calibrate(g2);
+ taxonLabelWidth = taxonLabelPainter.getWidth(g2, node);
+ }
+ AffineTransform taxonTransform = taxonLabelTransforms.get(taxon);
+ Painter.Justification taxonLabelJustification = taxonLabelJustifications.get(taxon);
+ g2.transform(taxonTransform);
+
+ final Rectangle2D.Double bounds = new Rectangle2D.Double(0.0, 0.0, taxonLabelWidth, taxonLabelPainter.getPreferredHeight());
+ taxonLabelPainter.paint(g2, node, taxonLabelJustification, bounds);
+
+ g2.setTransform(oldTransform);
+ }
+ }
+ }
+
+ final Node rootNode = tree.getRootNode();
+ final boolean nodesLables = nodeLabelPainter != null && nodeLabelPainter.isVisible();
+ final boolean branchLables = branchLabelPainter != null && branchLabelPainter.isVisible();
+
+ for(int nn = nodesInOrder.size()-1; nn >= 0; --nn) {
+ final Node node = nodesInOrder.get(nn);
+
+ if (showingRootBranch || node != rootNode) {
+ if( !isNodeVisible(node) ) continue;
+ if( hideNode(node) ) continue;
+
+ if( !tree.isExternal(node) ) {
+ final Shape branchPath = transform.createTransformedShape(treeLayout.getBranchPath(node));
+ g2.setStroke(branchLineStroke);
+
+ final Paint paint =
+ branchDecorator != null ? branchDecorator.getBranchPaint(tree, node) : Color.BLACK;
+
+ g2.setPaint(paint);
+
+ // todo: although this fix is only an if != null check, this is ok because the missing node is a root node,
+ // todo: which should not be drawn on an unrooted view anyway
+ if(branchPath == null)
+ continue;
+
+ g2.draw(branchPath);
+
+ if(drawNodes)
+ nodeMarker(g2, node);
+
+ if (preElementDrawCode && nodesLables) {
+ final AffineTransform nodeTransform = nodeLabelTransforms.get(node);
+ if (nodeTransform != null) {
+ final Painter.Justification nodeLabelJustification = nodeLabelJustifications.get(node);
+ g2.transform(nodeTransform);
+
+ final Rectangle2D.Double bounds = new Rectangle2D.Double(0.0, 0.0,
+ nodeLabelPainter.getWidth(g2, node), nodeLabelPainter.getPreferredHeight());
+ nodeLabelPainter.paint(g2, node, nodeLabelJustification, bounds);
+
+ g2.setTransform(oldTransform);
+ }
+ }
+ }
+
+
+ if ( branchLables && preElementDrawCode ) {
+ final AffineTransform branchTransform = branchLabelTransforms.get(node);
+ if (branchTransform != null) {
+ g2.transform(branchTransform);
+
+ branchLabelPainter.calibrate(g2);
+ final double preferredWidth = branchLabelPainter.getWidth(g2, node);
+ final double preferredHeight = branchLabelPainter.getPreferredHeight();
+
+ branchLabelPainter.paint(g2, node, Painter.Justification.CENTER,
+ new Rectangle2D.Double(0, 0, preferredWidth, preferredHeight));
+
+ g2.setTransform(oldTransform);
+ }
+ }
+ }
+ }
+
+ if( ! preElementDrawCode ) {
+ for( TreeDrawableElement e : treeElements ) {
+ if(e.isVisible())
+ e.draw(g2, clipOfscreenShapes ? viewport : null);
+ }
+ }
+
+ if( ! hideNode(rootNode) && drawNodes ) {
+ g2.setStroke(branchLineStroke);
+ nodeMarker(g2, rootNode);
+ }
+
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.paint(g2, this, Painter.Justification.CENTER, scaleBarBounds);
+ }
+
+ g2.setStroke(oldStroke);
+ g2.setPaint(oldPaint);
+ g2.setFont(oldFont);
+
+ if( ! antialiasOn ) {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
+ }
+ }
+
+ private void calibrate(Graphics2D g2, double width, double height) {
+ long start = System.currentTimeMillis();
+
+ // First of all get the bounds for the unscaled tree
+ Rectangle2D treeBounds = null;
+
+ final Node rootNode = tree.getRootNode();
+
+ // todo efficency create a list once of none hidden nodes etc
+
+ // bounds on branches
+ for (Node node : tree.getNodes()) {
+ if( hideNode(node) ) continue;
+ // no root branch for unrooted trees
+ if( !(tree.conceptuallyUnrooted() && (node == rootNode)) ) {
+ final Shape branchPath = treeLayout.getBranchPath(node);
+ // Add the bounds of the branch path to the overall bounds
+ final Rectangle2D branchBounds = branchPath.getBounds2D();
+ if (treeBounds == null) {
+ treeBounds = branchBounds;
+ } else {
+ treeBounds.add(branchBounds);
+ }
+ }
+ }
+
+ assert treeBounds != null; //the code below is a hack to make this not crash for users
+ if(treeBounds == null){
+ if(calibrated)
+ return;
+ treeBounds = new Rectangle2D.Double(0,0,100,100); //this is here so the treeViewer draws somethihng...
+ }
+
+ boolean oldScaleCode = false;
+
+ // oldScaleCode too
+ final Rectangle2D bounds = treeBounds.getBounds2D(); // (JH) same as (Rectangle2D) treeBounds.clone();
+
+ double scaleHeight = 0;
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.calibrate(g2);
+ scaleHeight = scaleBarPainter.getPreferredHeight();
+ }
+
+ // area available for drawing
+ final double availableW = width - (insets.left + insets.right);
+ final double availableH = height - (insets.top + insets.bottom + scaleHeight);
+
+ final Set<Node> externalNodes = tree.getExternalNodes();
+ Node nodeWithLongestTaxon = null;
+
+ TreeBoundsHelper tbh =
+ new TreeBoundsHelper(externalNodes.size() + 2*tree.getNodes().size(), availableW, availableH,
+ treeBounds);
+
+ if (taxonLabelPainter != null && taxonLabelPainter.isVisible()) {
+
+ taxonLabelWidth = 0.0;
+ taxonLabelPainter.calibrate(g2);
+
+ if( treeLayout.alignTaxa() ) {
+ // Find the longest taxon label
+ for (Node node : externalNodes) {
+ final double preferredWidth = taxonLabelPainter.getWidth(g2, node);
+ if( preferredWidth > taxonLabelWidth ) {
+ taxonLabelWidth = preferredWidth;
+ nodeWithLongestTaxon = node;
+ }
+ }
+ }
+
+ final double labelHeight = taxonLabelPainter.getPreferredHeight();
+
+ for (Node node : externalNodes) {
+ if( hideNode(node) ) continue;
+
+ if( nodeWithLongestTaxon == null ) {
+ taxonLabelPainter.calibrate(g2);
+ taxonLabelWidth = taxonLabelPainter.getWidth(g2, node);
+ }
+ // Get the line that represents the orientation for the taxon label
+ final Line2D taxonPath = treeLayout.getTaxonLabelPath(node);
+
+ //System.out.println("For " + tree.getTaxon(node).getName())
+ tbh.addBounds(taxonPath, labelHeight, labelXOffset + taxonLabelWidth, false);
+
+ if( oldScaleCode ) {
+ final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, taxonLabelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform taxonTransform = calculateTransform(null, taxonPath, taxonLabelWidth, labelHeight, true);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(taxonTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+ }
+
+
+ if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
+
+ for( Node node : tree.getNodes() ) {
+ if( hideNode(node) ) continue;
+
+ // Get the line that represents the label orientation
+ final Line2D labelPath = treeLayout.getNodeLabelPath(node);
+
+ if (labelPath != null) {
+ nodeLabelPainter.calibrate(g2);
+ final double labelHeight = nodeLabelPainter.getPreferredHeight();
+ final double labelWidth = nodeLabelPainter.getWidth(g2, node);
+
+ tbh.addBounds(labelPath, labelHeight, labelXOffset + labelWidth, false);
+
+ if( oldScaleCode ) {
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, true);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+ }
+ }
+
+ if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
+ // Iterate though the nodes
+ for (Node node : tree.getNodes()) {
+ if( hideNode(node) ) continue;
+
+ // Get the line that represents the path for the branch label
+ final Line2D labelPath = treeLayout.getBranchLabelPath(node);
+
+ if (labelPath != null) {
+ branchLabelPainter.calibrate(g2);
+ final double labelHeight = branchLabelPainter.getHeightBound();
+ final double labelWidth = branchLabelPainter.getWidth(g2, node);
+
+ tbh.addBounds(labelPath, labelHeight, labelXOffset + labelWidth, true);
+
+ if( oldScaleCode ) {
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, false);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+ }
+ }
+
+ if( oldScaleCode ) {
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.calibrate(g2);
+ scaleBarBounds = new Rectangle2D.Double(treeBounds.getX(), treeBounds.getY(),
+ treeBounds.getWidth(), scaleBarPainter.getPreferredHeight());
+ bounds.add(scaleBarBounds);
+ }
+ }
+
+ if( oldScaleCode ) {
+ assert false;
+ //availableH = height - insets.top - insets.bottom;
+ }
+
+ final double[] doubles = tbh.getOrigionAndScale(false);
+ double yorigion = doubles[0];
+ double yScale = doubles[1];
+
+ final double[] xdoubles = tbh.getOrigionAndScale(true);
+ double xorigion = xdoubles[0];
+ double xScale = xdoubles[1];
+
+ // oldscalecode too ************************** vvvvvvvvvvvvvvvvvvvvvvvvvvv
+
+ // get the difference between the tree's bounds and the overall bounds, i.e. the amount (in pixels) required
+ // to hold non-scaling stuff located outside the tree
+
+ double xDiff = bounds.getWidth() - treeBounds.getWidth();
+ double yDiff = bounds.getHeight() - treeBounds.getHeight();
+ assert xDiff >= 0 && yDiff >= 0;
+
+ // small tree, long labels, label bounds may get larger that window, protect against that
+
+ if( xDiff >= availableW ) {
+ xDiff = Math.min(availableW, bounds.getWidth()) - treeBounds.getWidth();
+ }
+
+ if( yDiff >= availableH ) {
+ yDiff = Math.min(availableH, bounds.getHeight()) - treeBounds.getHeight();
+ }
+
+ // Get the amount of canvas that is going to be taken up by the tree -
+ // The rest is taken up by taxon labels which don't scale
+ final double w = availableW - xDiff;
+ final double h = availableH - yDiff;
+ // oldscalecode too ************************** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ double xOffset = 0.0;
+ double yOffset = 0.0;
+
+ if (treeLayout.maintainAspectRatio()) {
+ // If the tree is layed out in both dimensions then we
+ // need to find out which axis has the least space and scale
+ // the tree to that (to keep the aspect ratio.
+ if( oldScaleCode ) {
+ final boolean widthLimit = (w / treeBounds.getWidth()) < (h / treeBounds.getHeight());
+ final double scale = widthLimit ? w / treeBounds.getWidth() : h / treeBounds.getHeight();
+ treeScale = xScale = yScale = scale;
+
+ // and set the origin so that the center of the tree is in
+ // the center of the canvas
+ xOffset = ((width - (treeBounds.getWidth() * xScale)) / 2) - (treeBounds.getX() * xScale);
+ yOffset = ((height - (treeBounds.getHeight() * yScale)) / 2) - (treeBounds.getY() * yScale);
+ } else {
+
+ if( tbh.getRange(true, xorigion, yScale) <= availableW ) {
+ //if( xorigion + yScale * treeBounds.getWidth() <= availableW ) {
+ xorigion = tbh.getOrigion(true, yScale);
+ treeScale = yScale;
+ } else {
+ double size = 0;
+ int count = 0;
+ String oldValues = "";
+ //count is here to make sure we don't get an infinite loop if there is no scale that will
+ //allow the tree to be contained in the current view
+ while((size = tbh.getRange(false, yorigion, xScale)) > availableH && count < 10){
+ xScale *= availableH/size;
+ yorigion = yOffset;
+ count++;
+ }
+ //todo: this was removed assert tbh.getRange(false, yorigion, xScale) <= availableH : tbh.getRange(false, yorigion, xScale)+" : "+availableH+" : "+oldValues;
+ //assert yorigion + xScale * treeBounds.getHeight() <= availableH;
+ yorigion = tbh.getOrigion(false, xScale);
+ treeScale = xScale;
+ }
+
+ //System.out.println("xs/ys " + xScale + "/" + yScale + " (" + treeScale + ")" + " xo/yo " + xorigion + "/" + yorigion);
+ xScale = yScale = treeScale;
+
+ xOffset = xorigion - treeBounds.getX() * treeScale;
+ yOffset = yorigion - treeBounds.getY() * treeScale;
+ double xRange = tbh.getRange(true, xorigion, treeScale);
+ final double dx = (availableW - xRange)/2;
+ xOffset += dx;
+ double yRange = tbh.getRange(false, yorigion, treeScale);
+ final double dy = (availableH - yRange)/2;
+ yOffset += dy; // > 0 ? dy : 0;
+ //System.out.println("xof/yof " + xOffset + "/" + yOffset);
+ }
+
+ } else {
+ // Otherwise just scale both dimensions
+
+// System.out.println("old/new xs " + (w / treeBounds.getWidth()) + "/" + xScale
+// + " ys " + (h / treeBounds.getHeight()) + "/" + yScale
+// + " y0 " + -bounds.getY() + "/" + yOffset + " x0 " + -bounds.getX() + "/" + xorigion);
+ if( oldScaleCode ) {
+ xScale = w / treeBounds.getWidth();
+ yScale = h / treeBounds.getHeight();
+
+ // and set the origin in the top left corner
+ xOffset = -bounds.getX();
+ yOffset = -bounds.getY();
+ } else {
+ xOffset = xorigion - treeBounds.getX() * xScale;
+ yOffset = yorigion - treeBounds.getY() * yScale;
+ }
+
+ treeScale = xScale;
+ }
+
+ assert treeScale > 0;
+
+ // Create the overall transform
+ transform = new AffineTransform();
+ transform.translate(xOffset + insets.left, yOffset + insets.top);
+ transform.scale(xScale, yScale);
+
+ final double xl = transform.getTranslateX() + transform.getScaleX() * treeBounds.getX();
+ final double xh = transform.getTranslateX() + transform.getScaleX() * treeBounds.getMaxX();
+
+ // Get the bounds for the actual scaled tree (not anymore)
+ //treeBounds = null;
+ {
+ Set<Node> small = new HashSet<Node>();
+ for (Node node : tree.getNodes()) {
+ if( hideNode(node) ) continue;
+
+// if (showingRootBranch || node != rootNode) {
+// final Shape branchPath = transform.createTransformedShape(treeLayout.getBranchPath(node));
+// final Rectangle2D bounds2D = branchPath.getBounds2D();
+// if (treeBounds == null) {
+// treeBounds = bounds2D;
+// } else {
+// treeBounds.add(bounds2D);
+// }
+// }
+
+ node.removeAttribute(clpsdName + "-auto");
+ if( ! small.contains(node) && treeLayout.smallSubTree(node, transform) ) {
+ node.setAttribute(clpsdName + "-auto", Boolean.TRUE);
+ small.addAll(Utils.getNodes(tree, node));
+ }
+ }
+ }
+
+ // Clear previous values of taxon label bounds and transforms
+ taxonLabelBounds.clear();
+ taxonLabelTransforms.clear();
+ taxonLabelJustifications.clear();
+ treeElements.clear();
+
+ if (taxonLabelPainter != null && taxonLabelPainter.isVisible()) {
+ final double labelHeight = taxonLabelPainter.getPreferredHeight();
+ Rectangle2D labelBounds = (nodeWithLongestTaxon == null) ? null :
+ new Rectangle2D.Double(0.0, 0.0, taxonLabelWidth, labelHeight);
+
+ // Iterate though the external nodes
+ for (Node node : externalNodes) {
+ if( hideNode(node) || !isNodeVisible(node) ) continue;
+
+ final Taxon taxon = tree.getTaxon(node);
+ if( nodeWithLongestTaxon == null ) {
+ taxonLabelPainter.calibrate(g2);
+ taxonLabelWidth = taxonLabelPainter.getWidth(g2, node);
+ labelBounds = new Rectangle2D.Double(0.0, 0.0, taxonLabelWidth, labelHeight);
+ }
+ // Get the line that represents the path for the taxon label
+ final Line2D taxonPath = treeLayout.getTaxonLabelPath(node);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform taxonTransform = calculateTransform(transform, taxonPath, taxonLabelWidth, labelHeight, true);
+
+ // Store the alignment in the map for use when drawing
+ final Painter.Justification just = (taxonPath.getX1() < taxonPath.getX2()) ?
+ Painter.Justification.LEFT : Painter.Justification.RIGHT;
+
+ if( preElementDrawCode ) {
+ // Store the transformed bounds in the map for use when selecting
+ taxonLabelBounds.put(taxon, taxonTransform.createTransformedShape(labelBounds));
+
+ // Store the transform in the map for use when drawing
+ taxonLabelTransforms.put(taxon, taxonTransform);
+
+ taxonLabelJustifications.put(taxon, just);
+ }
+
+ final TreeDrawableElementNodeLabel e =
+ new TreeDrawableElementNodeLabel(tree, node, just, labelBounds, taxonTransform, 10,
+ nodeWithLongestTaxon, (BasicLabelPainter) taxonLabelPainter,
+ null);
+
+ treeElements.add(e);
+ }
+ }
+
+ // Clear the map of individual node label bounds and transforms
+ nodeLabelBounds.clear();
+ nodeLabelTransforms.clear();
+ nodeLabelJustifications.clear();
+
+ if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
+ final double labelHeight = nodeLabelPainter.getPreferredHeight();
+
+ // Iterate though all nodes
+ for (Node node : tree.getNodes()) {
+ if( hideNode(node) || !isNodeVisible(node) ) continue;
+
+ // Get the line that represents the orientation of node label
+ final Line2D labelPath = treeLayout.getNodeLabelPath(node);
+
+ if (labelPath != null) {
+ final double labelWidth = nodeLabelPainter.getWidth(g2, node);
+ final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, true);
+
+ // Store the alignment in the map for use when drawing
+
+ Painter.Justification justification =
+ (labelPath.getX1() < labelPath.getX2()) ? Painter.Justification.LEFT : Painter.Justification.RIGHT;
+
+ if( preElementDrawCode ) {
+ // Store the transformed bounds in the map for use when selecting
+ nodeLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
+ // Store the transform in the map for use when drawing
+ nodeLabelTransforms.put(node, labelTransform);
+ nodeLabelJustifications.put(node, justification);
+ }
+
+ final TreeDrawableElementNodeLabel e =
+ new TreeDrawableElementNodeLabel(tree, node, justification, labelBounds, labelTransform, 9,
+ null, ((BasicLabelPainter) nodeLabelPainter), "node");
+
+ treeElements.add(e);
+ }
+ }
+ }
+
+ if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
+
+ // float sss = ((BasicLabelPainter)branchLabelPainter).getFontSize();
+ // for(int k = 0; k < 2; ++k) {
+// if(k == 0) ((BasicLabelPainter)branchLabelPainter).setFontSize(((BasicLabelPainter)branchLabelPainter).getFontMinSize(), false) ;
+// if(k == 1) ((BasicLabelPainter)branchLabelPainter).setFontSize(sss, false) ;
+
+ branchLabelPainter.calibrate(g2);
+ final double labelHeight = branchLabelPainter.getPreferredHeight();
+
+ //System.out.println("transform " + transform);
+
+ for( Node node : tree.getNodes() ) {
+ if( hideNode(node) || !isNodeVisible(node) ) continue;
+
+ // Get the line that represents the path for the branch label
+ final Line2D labelPath = treeLayout.getBranchLabelPath(node);
+
+ if (labelPath != null) {
+ final double labelWidth = branchLabelPainter.getWidth(g2, node);
+ final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ final double branchLength = labelPath.getP2().distance(labelPath.getP1());
+
+ final Painter.Justification just = labelPath.getX1() < labelPath.getX2() ? Painter.Justification.LEFT :
+ Painter.Justification.RIGHT;
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, false);
+
+// System.out.print( Utils.DEBUGsubTreeRep(Utils.rootTheTree(tree), node)
+// + " " + labelWidth + "x" + labelHeight + " " +
+// ((BasicLabelPainter)branchLabelPainter).getFontSize() + " " + labelTransform);
+
+ // move to middle of branch - since the move is before the rotation
+ // and center label by moving an extra half width of label
+ final double direction = just == Painter.Justification.RIGHT ? 1 : -1;
+ labelTransform.translate(-labelWidth/2 + -direction * xScale * branchLength / 2, -5 - labelHeight/2);
+
+ if( preElementDrawCode ) {
+ // Store the transformed bounds in the map for use when selecting (not anymore JH)
+ final Shape value = labelTransform.createTransformedShape(labelBounds);
+ //
+ branchLabelBounds.put(node, value);
+ // Store the transform in the map for use when drawing
+ branchLabelTransforms.put(node, labelTransform);
+ // unused at the moment
+ // Store the alignment in the map for use when drawing
+ //branchLabelJustifications.put(node, just);
+ }
+ // System.out.println(" -> " + labelTransform);
+ // if( k == 0 ) continue;
+
+ final TreeDrawableElementNodeLabel e =
+ new TreeDrawableElementNodeLabel(tree, node, Painter.Justification.CENTER, labelBounds, labelTransform, 8,
+ null, ((BasicLabelPainter) branchLabelPainter), "branch");
+
+ treeElements.add(e);
+ //}
+ }
+ }
+ }
+
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.calibrate(g2);
+ final double h1 = scaleBarPainter.getPreferredHeight();
+ final double x = xl; // treeBounds.getX()
+ final double wid = xh - xl; // treeBounds.getWidth();
+ scaleBarBounds = new Rectangle2D.Double(x, height - h1, wid, h1);
+ }
+
+ // unused at the moment
+ //calloutPaths.clear();
+
+ if( autoExpantion ) {
+ setTreeAttributesForAutoExpansion();
+ // some nodes may switched to non visible
+ for(int k = 0; k < treeElements.size(); ++k) {
+ final TreeDrawableElement e = treeElements.get(k);
+ assert ! hideNode(e.getNode());
+ if( !isNodeVisible(e.getNode() ) ) {
+ treeElements.remove(k);
+ --k;
+ }
+ }
+ }
+
+ long now = System.currentTimeMillis();
+ TreeDrawableElement.setOverlappingVisiblitiy(treeElements, g2);
+ System.err.println("Clash " + (System.currentTimeMillis() - now));
+
+ calibrated = true;
+
+ System.err.println("Calibrate " + (System.currentTimeMillis() - start));
+ }
+
+
+ private AffineTransform calculateTransform(AffineTransform globalTransform, Line2D line,
+ double width, double height, boolean just) {
+ final Point2D origin = line.getP1();
+ if (globalTransform != null) {
+ globalTransform.transform(origin, origin);
+ }
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform lineTransform = new AffineTransform();
+
+ final double dy = line.getY2() - line.getY1();
+ // efficency
+ if( dy != 0.0 ) {
+ final double dx = line.getX2() - line.getX1();
+ final double angle = dx != 0.0 ? Math.atan(dy / dx) : 0.0;
+ lineTransform.rotate(angle, origin.getX(), origin.getY());
+ }
+
+ // Now add a translate to the transform - if it is on the left then we need
+ // to shift it by the entire width of the string.
+ final double ty = origin.getY() - (height / 2.0);
+ double tx = origin.getX();
+ if( just) {
+ if (!just || line.getX2() > line.getX1()) {
+ tx += labelXOffset;
+ } else {
+ tx -= (labelXOffset + width);
+ }
+ }
+ lineTransform.translate(tx, ty);
+ return lineTransform;
+ }
+
+ public void setViewPort(JViewport viewport) {
+ this.viewport = viewport;
+ }
+
+ TreeLayout getTreeLayout() {
+ return treeLayout;
+ }
+
+ private class TreeBoundsHelper {
+ private double[] xbounds;
+ private double[] ybounds;
+ int nv;
+ double availableW;
+ double availableH;
+ Rectangle2D treeBounds;
+
+ public TreeBoundsHelper(int nValues, double availableW, double availableH, Rectangle2D treeBounds) {
+ // each value imposes a constraints (3 numbers) + 2 for raw height and width
+ nValues = 3 * (nValues + 2);
+ xbounds = new double[nValues];
+ ybounds = new double[nValues];
+ nv = 0;
+ this.availableH = availableH;
+ this.availableW = availableW;
+ this.treeBounds = treeBounds;
+
+ xbounds[nv] = treeBounds.getWidth();
+ xbounds[nv+1] = availableW;
+ xbounds[nv+2] = 0;
+ ybounds[nv] = treeBounds.getHeight();
+ ybounds[nv+1] = availableH;
+ ybounds[nv+2] = 0;
+ nv += 3;
+ xbounds[nv] = 0;
+ xbounds[nv+1] = availableW;
+ xbounds[nv+2] = 0;
+ ybounds[nv] = 0;
+ ybounds[nv+1] = availableH;
+ ybounds[nv+2] = 0;
+ nv += 3;
+ }
+
+ private int quadrantOf(Line2D line, double[] sincos) {
+ Point2D start = line.getP1();
+ Point2D end = line.getP2();
+ double dy = end.getY() - start.getY();
+ double dx = end.getX() - start.getX();
+ double r = Math.sqrt(dx * dx + dy * dy);
+ sincos[0] = dy / r;
+ sincos[1] = dx / r;
+ return (dy>=0 ? 0 : 2) + ((dy>=0) == (dx>=0) ? 0 : 1);
+ }
+
+ // v, height - y-extra(max), -y-extra(min)
+ void addBounds(Line2D taxonPath, double labelHeight, double labelWidth, boolean centered) {
+ double[] sincos = {0.0, 1.0};
+
+ int quad = quadrantOf(taxonPath, sincos); // sine and cosine of the inclination of the taxonPath vector
+ final double yHigh = labelHeight / 2;
+ final double xHigh = centered ? labelWidth / 2 : labelWidth;
+ final double xLow = centered ? -xHigh : 0;
+ // origin here before rotate is midpoint on left edge for non centered, center for centered
+ // order is counter-clockwise from upper right corner, which makes the corner number match the quadrant number
+ // for max X. other limits are relative to that.
+
+ double[] pts = {xHigh, yHigh, xLow, yHigh, xLow, -yHigh, xHigh, -yHigh}; // four corners of bounding box
+ int ixmax = 2*quad;
+ int ixmin = 2*((quad+2) & 0x3);
+ int iymax = 2*((quad+3) & 0x3);
+ int iymin = 2*((quad+1) & 0x3);
+
+ final double thesin = -sincos[0];
+ final double thecos = sincos[1];
+
+ double dx = thecos * pts[ixmax] - thesin * pts[ixmax + 1];
+ if( Double.isNaN(dx) ) {
+ assert dx >= 0 : dx + " " + thecos + " " + thesin;
+ }
+
+ final Point2D start = taxonPath.getP1();
+ final Point2D end = taxonPath.getP2();
+ final double xInTreeAbs = centered ? (start.getX() + end.getX()) / 2 : start.getX();
+ // yikes - some code determining the paths uses floats, so when used with doubles small discrapencies can make
+ // valus small negatives
+ double x = (float)xInTreeAbs - (float)treeBounds.getMinX(); assert x >= 0 : x;
+ xbounds[nv] = x;
+ xbounds[nv+1] = availableW - dx;
+ dx = -(thecos * pts[ixmin] - thesin * pts[ixmin + 1]); assert dx >= 0 : dx;
+ xbounds[nv+2] = dx;
+
+ double dy = -(thesin * pts[iymax] + thecos * pts[iymax+1]); assert dy >= 0;
+ // y0 + scale(y) * y + y-extra <= height
+ // y0 + scale(y) * y + y-extra >= 0
+
+ final double yTreeAbs = centered ? (start.getY() + end.getY()) / 2 : start.getY();
+ double y = (float) yTreeAbs - (float)treeBounds.getMinY(); assert y >= 0 : y;
+ ybounds[nv] = y;
+ ybounds[nv+1] = availableH - dy;
+
+ dy = thesin * pts[iymin] + thecos * pts[iymin+1]; assert dy >= 0;
+ ybounds[nv+2] = dy;
+
+ nv += 3;
+ }
+
+ double[] getOrigionAndScale(boolean isx) {
+ double[] values = isx ? xbounds : ybounds;
+
+ double scale = Double.MAX_VALUE;
+ double origin = 0.0;
+ double minOrigin = Double.MAX_VALUE;
+ for(int k = 0; k < nv; k += 3) {
+ if( values[k] == 0.0 ) {
+ origin = Math.max(origin, values[k+2]);
+ }
+ minOrigin = Math.min(minOrigin, values[k+1]);
+ }
+
+ if( origin > minOrigin ) {
+ origin = minOrigin/2;
+ }
+
+ int nit = 0;
+ while( nit < 100 ) {
+ ++nit; // safty net, should converge long before that
+ double scaleMin = -Double.MAX_VALUE;
+ for(int k = 0; k < nv; k += 3) {
+ if( values[k] != 0.0 ) {
+ double lim = Math.abs((values[k+1] - origin) / values[k]);
+ scale = Math.min(scale, lim);
+ }
+
+ double d = (values[k + 2] - origin);
+ // do limit only if y0 is no suffcient in this case
+ if( d > 0 ) {
+ double lim = d / values[k];
+ scaleMin = Math.max(scaleMin, lim);
+ }
+ }
+
+ boolean b = scaleMin <= scale || nit > 10 && scaleMin - scale < 1e-5;
+ if( origin < minOrigin && b) {
+ break;
+ }
+ origin = -Double.MAX_VALUE;
+ for(int k = 0; k < nv; k += 3) {
+ double l = values[k + 2] - scale * values[k];
+ origin = Math.max(origin, l);
+ }
+ }
+ assert scale > 0 : scale + " " + nit;
+ assert origin >= 0.0 : origin + " " + nit;
+ double[] r = {origin, scale};
+ return r;
+ }
+
+ double getOrigion(boolean isx, double scale) {
+ double origin = -Double.MAX_VALUE;
+ double[] values = isx ? xbounds : ybounds;
+ for(int k = 0; k < nv; k += 3) {
+ double l = values[k + 2] - scale * values[k];
+ origin = Math.max(origin, l);
+ }
+ return origin;
+ }
+
+ double getRange(boolean isx, double origin, double scale) {
+ double[] values = isx ? xbounds : ybounds;
+ double target = isx ? availableW : availableH;
+
+ double mx = -Double.MAX_VALUE, mn = Double.MAX_VALUE;
+
+ for(int k = 0; k < nv; k += 3) {
+ final double v = origin + values[k] * scale;
+ mx = Math.max(mx, v + (target - values[k+1]));
+ mn = Math.min(mn, v - values[k+2]);
+ }
+ return mx - mn;
+ }
+ }
+
+
+ // Overridden methods to recalibrate tree when bounds change
+ public void setBounds(int x, int y, int width, int height) {
+ // when moving the viewport x/y change
+ final Rectangle rectangle = getBounds();
+ calibrated = calibrated && width == rectangle.width && height == rectangle.height;
+ super.setBounds(x, y, width, height);
+ }
+
+ public void setBounds(Rectangle rectangle) {
+ calibrated = false;
+ super.setBounds(rectangle);
+ }
+
+ public void setSize(Dimension dimension) {
+ calibrated = false;
+ super.setSize(dimension);
+ }
+
+ public void setSize(int width, int height) {
+ calibrated = false;
+ super.setSize(width, height);
+ }
+
+ private JViewport viewport = null;
+
+ // Tree passed in
+ private RootedTree originalTree = null;
+ // Tree possibly transformed by the viewer
+ private RootedTree tree = null;
+ private List<Node> nodesInOrder;
+
+ private TreeLayout treeLayout = null;
+
+ private boolean orderBranches = false;
+ private SortedRootedTree.BranchOrdering branchOrdering = SortedRootedTree.BranchOrdering.INCREASING_NODE_DENSITY;
+
+ private boolean transformBranches = false;
+ private TransformedRootedTree.Transform branchTransform = TransformedRootedTree.Transform.CLADOGRAM;
+
+
+ private double treeScale;
+
+ //private Insets margins = new Insets(6, 6, 6, 6);
+ private Insets insets = new Insets(6, 6, 6, 6);
+
+ private Set<Node> selectedNodes = new HashSet<Node>();
+ private Set<Taxon> selectedTaxa = new HashSet<Taxon>();
+
+ private double rulerHeight = -1.0;
+ private Rectangle2D dragRectangle = null;
+
+ private BranchDecorator branchDecorator = null;
+
+ private float labelXOffset = 5.0F;
+ private Painter<Node> taxonLabelPainter = null;
+ private double taxonLabelWidth;
+ private Painter<Node> nodeLabelPainter = null;
+ private Painter<Node> branchLabelPainter = null;
+
+ private Painter<TreePane> scaleBarPainter = null;
+ private Rectangle2D scaleBarBounds = null;
+
+ private Stroke branchLineStroke = new BasicStroke(1.0F);
+ private Stroke collapsedStroke = new BasicStroke(1.5F);
+ private Stroke taxonCalloutStroke = new BasicStroke(0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{0.5f, 2.0f}, 0.0f);
+ private Stroke selectionStroke = new BasicStroke(6.0F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ private Paint selectionPaint = Color.BLUE; // new Color(180, 213, 254);
+ //private Color selectionPaint = new Color(180, 213, 254);
+ private boolean calibrated = false;
+
+ // Transform which scales the tree from it's own units to pixles and moves it to center of window
+ private AffineTransform transform = null;
+
+ private boolean showingRootBranch = true;
+ private boolean autoExpantion = false;
+ private boolean showingTaxonCallouts = true;
+
+ private Map<Taxon, AffineTransform> taxonLabelTransforms = new HashMap<Taxon, AffineTransform>();
+ private Map<Taxon, Shape> taxonLabelBounds = new HashMap<Taxon, Shape>();
+ private Map<Taxon, Painter.Justification> taxonLabelJustifications = new HashMap<Taxon, Painter.Justification>();
+
+ private Map<Node, AffineTransform> nodeLabelTransforms = new HashMap<Node, AffineTransform>();
+ private Map<Node, Shape> nodeLabelBounds = new HashMap<Node, Shape>();
+ private Map<Node, Painter.Justification> nodeLabelJustifications = new HashMap<Node, Painter.Justification>();
+
+ private Map<Node, AffineTransform> branchLabelTransforms = new HashMap<Node, AffineTransform>();
+ private Map<Node, Shape> branchLabelBounds = new HashMap<Node, Shape>();
+
+ private List<TreeDrawableElement> treeElements = new ArrayList<TreeDrawableElement>();
+
+ // unused at the moment
+ //private Map<Node, Painter.Justification> branchLabelJustifications = new HashMap<Node, Painter.Justification>();
+
+ // unused at the moment
+ // private Map<Taxon, Shape> calloutPaths = new HashMap<Taxon, Shape>();
+
+ private String transformBanchesPREFSkey = "transformBranches";
+ private String branchTransformTypePREFSkey = "branchTransformType";
+ private String branchOrderingPREFSkey = "branchOrdering";
+ private String orderBranchesPREFSkey = "orderBranches";
+ private String showRootPREFSkey = "showRootBranch";
+ private String autoExPREFSkey = "autoExpansion";
+ private String viewSubtreePREFSkey = "viewSubtree";
+ private String branchWeightPREFSkey = "branchWeight";
+ private static Preferences PREFS = Preferences.userNodeForPackage(TreePane.class);
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreePaneRuler.java b/src/jebl/gui/trees/treeviewer/TreePaneRuler.java
new file mode 100644
index 0000000..a868ef3
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreePaneRuler.java
@@ -0,0 +1,56 @@
+package jebl.gui.trees.treeviewer;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePaneRuler.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public class TreePaneRuler implements MouseListener, MouseMotionListener {
+ public TreePaneRuler(TreePane treePane) {
+ this.treePane = treePane;
+ treePane.addMouseListener(this);
+ treePane.addMouseMotionListener(this);
+ }
+
+ public void mouseClicked(MouseEvent mouseEvent) {
+// double selectedHeight = treePane.getHeightAt((Graphics2D)treePane.getGraphics(), mouseEvent.getPoint());
+// if (!mouseEvent.isShiftDown()) {
+// treePane.clearSelection();
+// }
+//
+// treePane.addSelectedHeight(isShiftDown);
+ }
+
+ public void mousePressed(MouseEvent mouseEvent) {
+ // This is used for dragging in combination with mouseDragged
+ // in the MouseMotionListener, below.
+ dragPoint = new Point2D.Double(mouseEvent.getPoint().getX(), mouseEvent.getPoint().getY());
+ }
+
+ public void mouseReleased(MouseEvent mouseEvent) {
+ }
+
+ public void mouseEntered(MouseEvent mouseEvent) {
+ treePane.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+ }
+
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ public void mouseMoved(MouseEvent mouseEvent) {
+ double height = treePane.getHeightAt((Graphics2D)treePane.getGraphics(), mouseEvent.getPoint());
+ treePane.setRuler(height);
+ }
+
+ public void mouseDragged(MouseEvent mouseEvent) {
+ }
+
+ private TreePane treePane;
+
+ private Point2D dragPoint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreePaneSelector.java b/src/jebl/gui/trees/treeviewer/TreePaneSelector.java
new file mode 100644
index 0000000..33c6bef
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreePaneSelector.java
@@ -0,0 +1,194 @@
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePaneSelector.java 614 2007-01-08 04:30:58Z pepster $
+ */
+public class TreePaneSelector implements MouseListener, MouseMotionListener {
+ public enum SelectionMode {
+ NODE,
+ CLADE,
+ TAXA
+ }
+
+ public enum DragMode {
+ SELECT,
+ SCROLL
+ }
+
+ public void setSelectionMode(SelectionMode selectionMode) {
+ this.selectionMode = selectionMode;
+ }
+
+ public void setDragMode(DragMode dragMode) {
+ this.dragMode = dragMode;
+ }
+
+ public TreePaneSelector(TreePane treePane) {
+ this.treePane = treePane;
+ treePane.addMouseListener(this);
+ treePane.addMouseMotionListener(this);
+ }
+
+ public void mouseClicked(MouseEvent mouseEvent) {
+ final Point mousePoint = mouseEvent.getPoint();
+ final Node[] selectedNode = treePane.getNodeAt(mousePoint);
+ final boolean doubleClick = mouseEvent.getClickCount() > 1;
+
+ final boolean addToSelection = mouseEvent.isShiftDown() || mouseEvent.isControlDown();
+
+ // keep selection on double click
+ if( ! doubleClick ) {
+ if ( ! addToSelection ) {
+ treePane.clearSelection();
+ }
+ }
+
+ SelectionMode mode = selectionMode;
+ if (mouseEvent.isAltDown()) {
+ switch( mode ) {
+ case NODE: mode = SelectionMode.CLADE; break;
+ case CLADE: mode = SelectionMode.NODE; break;
+ default: break;
+ }
+ }
+
+ final Node mainSelectedNode = selectedNode[0];
+
+ final boolean alreadySelected = treePane.getSelectedNodes().contains(mainSelectedNode);
+ switch (mode) {
+ case NODE:
+ treePane.addSelectedNode(mainSelectedNode, !(addToSelection && alreadySelected));
+ break;
+ case CLADE:
+
+ if( doubleClick ) {
+ if( mainSelectedNode != null )
+ treePane.toggleExpandContract(mainSelectedNode);
+ } else {
+ treePane.addSelectedClade(selectedNode, !(addToSelection && alreadySelected));
+ }
+ break;
+ case TAXA:
+ treePane.addSelectedTaxa(mainSelectedNode);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown SelectionMode: " + selectionMode.name());
+ }
+ }
+
+ public void mousePressed(MouseEvent mouseEvent) {
+ // This is used for dragging in combination with mouseDragged
+ // in the MouseMotionListener, below.
+ dragPoint = new Point2D.Double(mouseEvent.getPoint().getX(), mouseEvent.getPoint().getY());
+ }
+
+ public void mouseReleased(MouseEvent mouseEvent) {
+ if (treePane.getDragRectangle() != null) {
+ Set<Node> selectedNodes = treePane.getNodesAt((Graphics2D) treePane.getGraphics(), treePane.getDragRectangle().getBounds());
+
+ if (!mouseEvent.isShiftDown()) {
+ treePane.clearSelection();
+ }
+
+ SelectionMode mode = selectionMode;
+ if (mouseEvent.isAltDown()) {
+ if (mode == SelectionMode.NODE) {
+ mode = SelectionMode.CLADE;
+ } else if (mode == SelectionMode.CLADE) {
+ mode = SelectionMode.NODE;
+ }
+ }
+
+ for (Node selectedNode : selectedNodes) {
+ switch (mode) {
+ case NODE:
+ treePane.addSelectedNode(selectedNode, true);
+ break;
+ case CLADE:
+ treePane.addSelectedClade(new Node[]{selectedNode, null}, true);
+ break;
+ case TAXA:
+ treePane.addSelectedTaxa(selectedNode);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown SelectionMode: " + selectionMode.name());
+ }
+ }
+ }
+ treePane.setDragRectangle(null);
+ }
+
+ public void mouseEntered(MouseEvent mouseEvent) {
+ if (dragMode == DragMode.SCROLL || mouseEvent.isMetaDown()) {
+ treePane.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
+ } else {
+
+ treePane.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ public void mouseMoved(MouseEvent mouseEvent) {
+
+ }
+
+ public void mouseDragged(MouseEvent mouseEvent) {
+ //this situation can happen on MacOS, though very rare
+ if (dragPoint == null) {
+ return;
+ }
+ if (dragMode == DragMode.SCROLL || mouseEvent.isMetaDown()) {
+ // Calculate how far the mouse has been dragged from the point clicked in
+ // mousePressed, above.
+ int deltaX = (int) (mouseEvent.getX() - dragPoint.getX());
+ int deltaY = (int) (mouseEvent.getY() - dragPoint.getY());
+
+ // Get the currently visible window
+ Rectangle visRect = treePane.getVisibleRect();
+
+ // Calculate how much we need to scroll
+ if (deltaX > 0) {
+ deltaX = visRect.x - deltaX;
+ } else {
+ deltaX = visRect.x + visRect.width - deltaX;
+ }
+
+ if (deltaY > 0) {
+ deltaY = visRect.y - deltaY;
+ } else {
+ deltaY = visRect.y + visRect.height - deltaY;
+ }
+
+ // Scroll the visible region
+ Rectangle r = new Rectangle(deltaX, deltaY, 1, 1);
+ treePane.scrollRectToVisible(r);
+ } else {
+ double x1 = Math.min(dragPoint.getX(), mouseEvent.getPoint().getX());
+ double y1 = Math.min(dragPoint.getY(), mouseEvent.getPoint().getY());
+ double x2 = Math.max(dragPoint.getX(), mouseEvent.getPoint().getX());
+ double y2 = Math.max(dragPoint.getY(), mouseEvent.getPoint().getY());
+ treePane.setDragRectangle(new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1));
+ treePane.scrollPointToVisible(mouseEvent.getPoint());
+ }
+ }
+
+ private TreePane treePane;
+
+ private SelectionMode selectionMode = SelectionMode.CLADE;
+
+ private DragMode dragMode = DragMode.SELECT;
+ private Point2D dragPoint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreeSelectionListener.java b/src/jebl/gui/trees/treeviewer/TreeSelectionListener.java
new file mode 100644
index 0000000..25c69e3
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreeSelectionListener.java
@@ -0,0 +1,10 @@
+package jebl.gui.trees.treeviewer;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeSelectionListener.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public interface TreeSelectionListener {
+
+ void selectionChanged();
+}
diff --git a/src/jebl/gui/trees/treeviewer/TreeViewer.java b/src/jebl/gui/trees/treeviewer/TreeViewer.java
new file mode 100644
index 0000000..9dd1783
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/TreeViewer.java
@@ -0,0 +1,720 @@
+/*
+ * AlignmentPanel.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.gui.trees.treeviewer;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.io.NexusExporter;
+import jebl.evolution.io.NexusImporter;
+import jebl.evolution.io.TreeImporter;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.Utils;
+import jebl.gui.trees.treeviewer.decorators.BranchDecorator;
+import jebl.gui.trees.treeviewer.painters.BasicLabelPainter;
+import jebl.gui.trees.treeviewer.painters.Painter;
+import jebl.gui.trees.treeviewer.painters.ScaleBarPainter;
+import jebl.gui.trees.treeviewer.treelayouts.PolarTreeLayout;
+import jebl.gui.trees.treeviewer.treelayouts.RadialTreeLayout;
+import jebl.gui.trees.treeviewer.treelayouts.RectilinearTreeLayout;
+import jebl.gui.trees.treeviewer.treelayouts.TreeLayout;
+import jebl.util.NumberFormatter;
+import org.virion.jam.controlpanels.*;
+import org.virion.jam.panels.OptionsPanel;
+import org.virion.jam.util.IconUtils;
+import org.virion.jam.util.SimpleListener;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeViewer.java 689 2007-04-15 23:02:26Z stevensh $
+ */
+public class TreeViewer extends JPanel implements Printable {
+ public enum TreeLayoutType {
+ RECTILINEAR("Rectangle"),
+ POLAR("Polar"),
+ RADIAL("Radial");
+
+ TreeLayoutType(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+
+ public enum SearchType {
+ CONTAINS("Contains"),
+ STARTS_WITH("Starts with"),
+ ENDS_WITH("Ends with"),
+ MATCHES("Matches");
+
+ SearchType(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+
+ static final int defaultPaletteSize = 200;
+
+ static private String rootedTreeLayoutPrefKey = "treelayout_rooted";
+ static private String unrootedTreeLayoutPrefKey = "treelayout_unrooted";
+ static private String unrootedTreeAllLayoutsAllowedPrefKey = "treelayout_unrooted_allallowed";
+
+ /**
+ * Creates new TreeViewer
+ */
+ public TreeViewer() {
+ this(new BasicControlPalette(defaultPaletteSize, BasicControlPalette.DisplayMode.ONLY_ONE_OPEN, true), SwingConstants.LEFT);
+ }
+
+ public TreeViewer(int CONTROL_PALETTE_ALIGNMENT, BasicControlPalette.DisplayMode mode) {
+ this(new BasicControlPalette(defaultPaletteSize, mode, true), CONTROL_PALETTE_ALIGNMENT);
+ }
+
+ public TreeViewer(int CONTROL_PALETTE_ALIGNMENT) {
+ this(new BasicControlPalette(defaultPaletteSize, BasicControlPalette.DisplayMode.ONLY_ONE_OPEN, true), CONTROL_PALETTE_ALIGNMENT);
+ }
+
+ /**
+ * Creates new TreeViewer
+ */
+ public TreeViewer(ControlPalette controlPalette, int CONTROL_PALETTE_ALIGNMENT) {
+ setOpaque(false);
+ setLayout(new BorderLayout());
+
+ treePane = new TreePane();
+ treePane.setAutoscrolls(true); //enable synthetic drag events
+ listeners = new HashSet<SimpleListener>();
+
+ //make sure that the change listeners are fired when the treeSelectionListener fires
+ treePane.addTreeSelectionListener(new TreeSelectionListener(){
+ public void selectionChanged() {
+ fireChangeListeners();
+ }
+ });
+
+ JScrollPane scrollPane = new JScrollPane(treePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ scrollPane.setMinimumSize(new Dimension(150, 150));
+ treePane.setViewPort(scrollPane.getViewport());
+
+ scrollPane.setBorder(null);
+ viewport = scrollPane.getViewport();
+
+ this.controlPalette = controlPalette;
+ controlPalette.getPanel().setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY));
+
+// JPanel panel = new JPanel(new BorderLayout());
+// panel.add(controlPalette, BorderLayout.NORTH);
+
+// splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, scrollPane, controlPalette);
+// splitPane.setContinuousLayout(true);
+// splitPane.setOneTouchExpandable(true);
+// splitPane.setResizeWeight(1.0);
+// splitPane.setDividerLocation(0.95);
+//
+// add(splitPane, BorderLayout.CENTER);
+
+ add(scrollPane, BorderLayout.CENTER);
+
+ if (CONTROL_PALETTE_ALIGNMENT == SwingConstants.LEFT) {
+ add(controlPalette.getPanel(), BorderLayout.WEST);
+ } else {
+ add(controlPalette.getPanel(), BorderLayout.EAST);
+ }
+ setTreeLayoutType(TreeLayoutType.values()[PREFS.getInt(rootedTreeLayoutPrefKey, TreeLayoutType.RECTILINEAR.ordinal())]);
+
+ // This overrides MouseListener and MouseMotionListener to allow selection in the TreePane -
+ // It installs itself within the constructor.
+ treePaneSelector = new TreePaneSelector(treePane);
+
+ controlPalette.addControlsProvider(controlsProvider, false);
+ controlPalette.addControlsProvider(treePane, false);
+
+ controlPalette.addControlPanelListener(new ControlPaletteListener() {
+ public void controlsChanged() {
+ TreeViewer.this.controlPalette.setupControls();
+ validate();
+ repaint();
+ }
+ });
+ }
+
+ private String currentTreeLayoutPrefKey() {
+ return (tree.conceptuallyUnrooted() ? unrootedTreeLayoutPrefKey : rootedTreeLayoutPrefKey);
+ }
+
+ protected TreeLayoutType getDefaultTreeLayoutType() {
+ boolean isRooted = !tree.conceptuallyUnrooted();
+ TreeLayoutType defaultLayout = (isRooted ? TreeLayoutType.RECTILINEAR : TreeLayoutType.RADIAL);
+ String layoutPrefKey = currentTreeLayoutPrefKey();
+ return TreeLayoutType.values()[PREFS.getInt(layoutPrefKey, defaultLayout.ordinal())];
+ }
+
+ protected void setDefaultTreeLayoutType(TreeLayoutType treeLayoutType) {
+ String layoutPrefKey = currentTreeLayoutPrefKey();
+ PREFS.putInt(layoutPrefKey, treeLayoutType.ordinal());
+ }
+
+ private void fireChangeListeners(){
+ for(SimpleListener listener : listeners){
+ listener.objectChanged();
+ }
+ }
+
+ /**
+ *
+ * @param listener
+ * @return true if the supplied listener is not already attached
+ */
+ public boolean addChangeListener(SimpleListener listener){
+ return listeners.add(listener);
+ }
+
+ /**
+ *
+ * @param listener
+ * @return true if the supplied listener was attached
+ */
+ public boolean removeChangeListener(SimpleListener listener){
+ return listeners.remove(listener);
+ }
+
+ public void setTree(Tree inTree, int defaultLabelSize) {
+ final boolean isRooted = (inTree instanceof RootedTree);
+ if (isRooted) {
+ tree = (RootedTree) inTree;
+ } else {
+ tree = Utils.rootTheTree(inTree);
+ }
+// infoArea.setText("");
+ infoIsVisible = false;
+ // make this settable?
+ infoText = "";
+ NumberFormatter formatter = new NumberFormatter(4);
+ for( String an : inTree.getAttributeNames() ) {
+ if( ! an.startsWith("&") && !an.equals(NexusExporter.treeNameAttributeKey) ) {
+ Object o = inTree.getAttribute(an);
+ String v;
+ if( o instanceof Double ) {
+ v = formatter.getFormattedValue((Double) o);
+ } else {
+ v = o.toString();
+ }
+// infoArea.append(an + ": " + v + "\n");
+ infoText += an + ": " + v + "\n";
+ infoIsVisible = true;
+ }
+ }
+
+ treePane.setTree(tree, null);
+
+ BasicLabelPainter taxonLabelPainter =
+ new BasicLabelPainter("Tip Labels", tree, BasicLabelPainter.PainterIntent.TIP, defaultLabelSize);
+ taxonLabelPainter.setAttribute(BasicLabelPainter.TAXON_NAMES);
+ treePane.setTaxonLabelPainter(taxonLabelPainter);
+
+ BasicLabelPainter nodeLabelPainter =
+ new BasicLabelPainter("Node Labels", tree, BasicLabelPainter.PainterIntent.NODE, defaultLabelSize);
+
+ // don't show controls when there is nothing to choose from
+ treePane.setNodeLabelPainter(nodeLabelPainter.getAttributes().length > 0 ? nodeLabelPainter : null);
+
+ BasicLabelPainter branchLabelPainter =
+ new BasicLabelPainter("Branch Labels", tree, BasicLabelPainter.PainterIntent.BRANCH, defaultLabelSize);
+
+ treePane.setBranchLabelPainter(branchLabelPainter.getAttributes().length > 0 ? branchLabelPainter : null);
+ treePane.setScaleBarPainter(new ScaleBarPainter());
+
+ // load appropriate tree layout from preferences and set it
+ setTreeLayoutType(getDefaultTreeLayoutType());
+ }
+
+ public void setTree(Tree tree) {
+ setTree(tree, 6);
+ }
+
+ public TreePane getTreePane(){
+ return treePane;
+ }
+
+ public ControlPalette getControlPalette() {
+ return controlPalette;
+ }
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(TreeViewer.class);
+
+// private JTextArea infoArea = null;
+ private boolean infoIsVisible = false;
+ private String infoText = "";
+
+ private ControlsProvider controlsProvider = new ControlsProvider() {
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // do nothing
+ }
+
+ private void setExpansion() {
+ final boolean enabled = !treePane.maintainAspectRatio();
+ verticalExpansionLabel.setEnabled(enabled);
+ verticalExpansionSlider.setEnabled(enabled);
+ }
+
+ public java.util.List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ JPanel treeViewPanel = new JPanel();
+ treeViewPanel.setLayout(new BoxLayout(treeViewPanel, BoxLayout.LINE_AXIS));
+ final String imagePath = "/jebl/gui/trees/treeviewer/images/";
+ Icon rectangularTreeIcon = IconUtils.getIcon(this.getClass(), imagePath + "rectangularTree.png");
+ Icon polarTreeIcon = IconUtils.getIcon(this.getClass(), imagePath + "polarTree.png");
+ Icon radialTreeIcon = IconUtils.getIcon(this.getClass(), imagePath + "radialTree.png");
+ final JToggleButton toggle1 = new JToggleButton(rectangularTreeIcon);
+ final JToggleButton toggle2 = new JToggleButton(polarTreeIcon);
+ final JToggleButton toggle3 = new JToggleButton(radialTreeIcon);
+ toggle1.setToolTipText("Rooted tree layout");
+ toggle2.setToolTipText("Circular tree layout");
+ toggle3.setToolTipText("Unrooted tree layout");
+ toggle1.putClientProperty("Quaqua.Button.style", "toggleWest");
+ toggle2.putClientProperty("Quaqua.Button.style", "toggleCenter");
+ toggle3.putClientProperty("Quaqua.Button.style", "toggleEast");
+ ButtonGroup buttonGroup = new ButtonGroup();
+ buttonGroup.add(toggle1);
+ buttonGroup.add(toggle2);
+ buttonGroup.add(toggle3);
+
+ switch (getDefaultTreeLayoutType()) {
+ case RECTILINEAR: toggle1.setSelected(true); break;
+ case POLAR: toggle2.setSelected(true); break;
+ case RADIAL: toggle3.setSelected(true); break;
+ }
+
+ treeViewPanel.add(Box.createHorizontalStrut(0));
+
+ treeViewPanel.add(toggle1);
+ treeViewPanel.add(toggle2);
+ treeViewPanel.add(toggle3);
+ treeViewPanel.add(Box.createHorizontalStrut(0));
+ optionsPanel.addSpanningComponent(treeViewPanel);
+
+ if( tree.conceptuallyUnrooted() ) {
+ final JCheckBox allowCB = new JCheckBox("Enable all layouts for unrooted trees");
+ boolean allow = PREFS.getBoolean(unrootedTreeAllLayoutsAllowedPrefKey, false);
+ allowCB.setSelected(allow);
+ optionsPanel.addSpanningComponent(allowCB);
+ //allowCB.setToolTipText("Enable all layouts for unrooted trees");
+ allowCB.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ final boolean s = allowCB.isSelected();
+ toggle1.setEnabled(s);
+ toggle2.setEnabled(s);
+ toggle3.setEnabled(s);
+ PREFS.putBoolean(unrootedTreeAllLayoutsAllowedPrefKey, s);
+ if (!s) {
+ setAndStoreTreeLayoutType(TreeLayoutType.RADIAL);
+ setExpansion();
+ toggle1.setSelected(false);
+ toggle2.setSelected(false);
+ toggle3.setSelected(true);
+ }
+ fireChangeListeners();
+ }
+ } );
+
+ toggle1.setEnabled(allow);
+ toggle2.setEnabled(allow);
+ toggle3.setEnabled(allow);
+ }
+
+ zoomSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 0);
+ zoomSlider.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ zoomSlider.setPaintTicks(true);
+ zoomSlider.setPaintLabels(true);
+
+ final String zoomValuePrefKey = "zoomvalue";
+ final int zoomValue = PREFS.getInt(zoomValuePrefKey, 0);
+ zoomSlider.setValue(zoomValue);
+ zoom = ((double) zoomValue) / 100.0;
+ zoomPending = true;
+
+ zoomSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int value = zoomSlider.getValue();
+ setZoom(((double) value) / 100.0);
+ PREFS.putInt(zoomValuePrefKey, value);
+ fireChangeListeners();
+ }
+ });
+
+ optionsPanel.addComponentWithLabel("Zoom:", zoomSlider, true);
+
+ verticalExpansionSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 1000, 0);
+ verticalExpansionSlider.setPaintTicks(true);
+ verticalExpansionSlider.setPaintLabels(true);
+
+ final String expansionValuePrefKey = "vzoomvalue";
+ final int expansionValue = PREFS.getInt(expansionValuePrefKey, 0);
+ verticalExpansionSlider.setValue(expansionValue);
+ verticalExpansion = ((double)expansionValue) / 100.0;
+
+ verticalExpansionSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int value = verticalExpansionSlider.getValue();
+ setVerticalExpansion(((double) value) / 100.0);
+ PREFS.putInt(expansionValuePrefKey, value);
+ fireChangeListeners();
+ }
+ });
+
+ verticalExpansionLabel = new JLabel("Expansion:");
+ optionsPanel.addComponents(verticalExpansionLabel, false, verticalExpansionSlider, true);
+ setExpansion();
+
+ toggle1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (toggle1.isSelected())
+ setAndStoreTreeLayoutType(TreeLayoutType.RECTILINEAR);
+ setExpansion();
+ fireChangeListeners();
+ }
+ });
+ toggle2.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (toggle2.isSelected())
+ setAndStoreTreeLayoutType(TreeLayoutType.POLAR);
+ setExpansion();
+ fireChangeListeners();
+ }
+ });
+ toggle3.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (toggle3.isSelected())
+ setAndStoreTreeLayoutType(TreeLayoutType.RADIAL);
+ setExpansion();
+ fireChangeListeners();
+ }
+ });
+
+ controls = new Controls("General", optionsPanel, true);
+ }
+
+ controlsList.add(controls);
+
+ if (infoIsVisible) {
+ JPanel jPanel = new JPanel(new BorderLayout());
+ jPanel.setBorder(new EmptyBorder(5,5,5,5));
+ JTextArea infoArea = new JTextArea(infoText);
+ infoArea.setOpaque(false);
+ infoArea.setFont(new JLabel().getFont());
+ infoArea.setWrapStyleWord(true);
+ infoArea.setLineWrap(true);
+ infoArea.setEditable(false);
+ jPanel.add(infoArea, BorderLayout.CENTER);
+ Controls infoControls = new Controls("Info", jPanel, true);
+ infoControls.setVisible(infoIsVisible);
+ controlsList.add(infoControls);
+ }
+ //infoArea.setText("info");
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ zoomSlider.setValue((Integer) settings.getSetting("Zoom"));
+ verticalExpansionSlider.setValue((Integer) settings.getSetting("Expansion"));
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ settings.putSetting("Zoom", zoomSlider.getValue());
+ settings.putSetting("Expansion", verticalExpansionSlider.getValue());
+ }
+
+ private JSlider zoomSlider;
+ private JSlider verticalExpansionSlider;
+ private JLabel verticalExpansionLabel;
+
+ private Controls controls = null;
+
+ };
+
+ public TreeLayoutType getTreeLayoutType(){
+ TreeLayout layout = treePane.getTreeLayout();
+ if(layout instanceof RectilinearTreeLayout)
+ return TreeLayoutType.RECTILINEAR;
+ if(layout instanceof PolarTreeLayout)
+ return TreeLayoutType.POLAR;
+ if(layout instanceof RadialTreeLayout)
+ return TreeLayoutType.RADIAL;
+ //this should never happen
+ throw new RuntimeException("Unknown TreeLayoutType: " + layout);
+ }
+
+ public void setTreeLayoutType(TreeLayoutType treeLayoutType) {
+ TreeLayout treeLayout;
+ switch (treeLayoutType) {
+ case RECTILINEAR:
+ treeLayout = new RectilinearTreeLayout();
+ break;
+ case POLAR:
+ treeLayout = new PolarTreeLayout();
+ break;
+ case RADIAL:
+ treeLayout = new RadialTreeLayout();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown TreeLayoutType: " + treeLayoutType);
+ }
+ treePane.setTreeLayout(treeLayout);
+ }
+
+ protected void setAndStoreTreeLayoutType(TreeLayoutType treeLayoutType) {
+ setTreeLayoutType(treeLayoutType);
+ setDefaultTreeLayoutType(treeLayoutType);
+ }
+
+ public void setControlPanelVisible(boolean visible) {
+ controlPalette.getPanel().setVisible(visible);
+ }
+
+ public void setBranchDecorator(BranchDecorator branchDecorator) {
+ treePane.setBranchDecorator(branchDecorator);
+ }
+
+ public void setNodeLabelPainter(Painter<Node> nodeLabelPainter) {
+ treePane.setNodeLabelPainter(nodeLabelPainter);
+ }
+
+ private boolean zoomPending = false;
+ private double zoom = 0.0, verticalExpansion = 0.0;
+
+ public void setZoom(double zoom) {
+ this.zoom = zoom;
+ refreshZoom();
+ }
+
+ public void setVerticalExpansion(double verticalExpansion) {
+ this.verticalExpansion = verticalExpansion;
+ refreshZoom();
+ }
+
+ private void refreshZoom() {
+ setZoom(zoom, zoom + verticalExpansion);
+ }
+
+ public void setZoom(double xZoom, double yZoom) {
+
+ Dimension viewportSize = viewport.getViewSize();
+ Point position = viewport.getViewPosition();
+
+ Dimension extentSize = viewport.getExtentSize();
+ double w = extentSize.getWidth() * (1.0 + (10.0 * xZoom));
+ double h = extentSize.getHeight() * (1.0 + (10.0 * yZoom));
+
+ Dimension newSize = new Dimension((int) w, (int) h);
+ treePane.setPreferredSize(newSize);
+
+ double cx = position.getX() + (0.5 * extentSize.getWidth());
+ double cy = position.getY() + (0.5 * extentSize.getHeight());
+
+ double rx = ((double) newSize.getWidth()) / viewportSize.getWidth();
+ double ry = ((double) newSize.getHeight()) / viewportSize.getHeight();
+
+ double px = (cx * rx) - (extentSize.getWidth() / 2.0);
+ double py = (cy * ry) - (extentSize.getHeight() / 2.0);
+
+ Point newPosition = new Point((int) px, (int) py);
+ viewport.setViewPosition(newPosition);
+ treePane.revalidate();
+ }
+
+ public void selectTaxa(SearchType searchType, String searchString, boolean caseSensitive) {
+ treePane.clearSelection();
+
+ if (searchType == SearchType.MATCHES && !caseSensitive) {
+ throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+ }
+
+ String query = (caseSensitive ? searchString : searchString.toUpperCase());
+
+ for (Taxon taxon : tree.getTaxa()) {
+ String target = (caseSensitive ?
+ taxon.getName() : taxon.getName().toUpperCase());
+ switch (searchType) {
+ case CONTAINS:
+ if (target.contains(query)) {
+ treePane.addSelectedTaxon(taxon);
+ }
+ break;
+ case STARTS_WITH:
+ if (target.startsWith(query)) {
+ treePane.addSelectedTaxon(taxon);
+ }
+ break;
+ case ENDS_WITH:
+ if (target.endsWith(query)) {
+ treePane.addSelectedTaxon(taxon);
+ }
+ break;
+ case MATCHES:
+ if (target.matches(query)) {
+ treePane.addSelectedTaxon(taxon);
+ }
+ break;
+ }
+ }
+ }
+
+ public void selectNodes(String attribute, SearchType searchType, String searchString, boolean caseSensitive) {
+ treePane.clearSelection();
+
+ if (searchType == SearchType.MATCHES && !caseSensitive) {
+ throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+ }
+
+ String query = (caseSensitive ? searchString : searchString.toUpperCase());
+
+ for (Node node : tree.getNodes()) {
+ Object value = node.getAttribute(attribute);
+
+ if (value != null) {
+ String target = (caseSensitive ?
+ value.toString() : value.toString().toUpperCase());
+ switch (searchType) {
+ case CONTAINS:
+ if (target.contains(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case STARTS_WITH:
+ if (target.startsWith(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case ENDS_WITH:
+ if (target.endsWith(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case MATCHES:
+ if (target.matches(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ public void clearSelectedTaxa() {
+ treePane.clearSelection();
+ }
+
+ public void setSelectionMode(TreePaneSelector.SelectionMode selectionMode) {
+ treePaneSelector.setSelectionMode(selectionMode);
+ }
+
+ public void setDragMode(TreePaneSelector.DragMode dragMode) {
+ treePaneSelector.setDragMode(dragMode);
+ }
+
+ public JComponent getExportableComponent() {
+ return treePane;
+ }
+
+ public void paint(Graphics g) {
+ if( zoomPending ) {
+ refreshZoom();
+ zoomPending = false;
+ }
+ super.paint(g);
+ }
+
+ protected RootedTree tree = null;
+
+ protected TreePane treePane;
+ protected TreePaneSelector treePaneSelector;
+
+ protected JViewport viewport;
+ protected JSplitPane splitPane;
+ private ControlPalette controlPalette;
+ private Set<SimpleListener> listeners;
+
+ static public void main(String[] args) {
+
+ JFrame frame = new JFrame("TreeViewer Test");
+ TreeViewer treeViewer = new TreeViewer();
+
+ try {
+ File inputFile = null;
+
+ if (args.length > 0) {
+ inputFile = new File(args[0]);
+ }
+
+ if (inputFile == null) {
+ // No input file name was given so throw up a dialog box...
+ java.awt.FileDialog chooser = new java.awt.FileDialog(frame, "Select NEXUS Tree File",
+ java.awt.FileDialog.LOAD);
+ chooser.setVisible(true);
+ inputFile = new java.io.File(chooser.getDirectory(), chooser.getFile());
+ chooser.dispose();
+ }
+
+ assert inputFile != null;
+
+// TreeImporter importer = new NewickImporter(new FileReader(inputFile));
+ Reader reader = new BufferedReader(new FileReader(inputFile));
+ TreeImporter importer = new NexusImporter(reader);
+ Tree tree = importer.importNextTree();
+ reader.close();
+ treeViewer.setTree(tree);
+ } catch (Exception ie) {
+ ie.printStackTrace();
+ System.exit(1);
+ }
+
+ frame.getContentPane().add(treeViewer, BorderLayout.CENTER);
+ frame.setVisible(true);
+ }
+
+ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
+ return treePane.print(graphics, pageFormat, pageIndex);
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer/decorators/AttributeBranchDecorator.java b/src/jebl/gui/trees/treeviewer/decorators/AttributeBranchDecorator.java
new file mode 100644
index 0000000..c9e9b97
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/decorators/AttributeBranchDecorator.java
@@ -0,0 +1,36 @@
+package jebl.gui.trees.treeviewer.decorators;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Tree;
+
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: AttributeBranchDecorator.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public class AttributeBranchDecorator implements BranchDecorator {
+ public AttributeBranchDecorator(String attributeName, Map<Object, Paint> paintMap) {
+ this.attributeName = attributeName;
+ this.paintMap = paintMap;
+ }
+
+ public Paint getBranchPaint(Tree tree, Node node) {
+ Paint paint = getPaint(node.getAttribute(attributeName));
+ if (paint == null) return Color.BLACK;
+ return paint;
+ }
+
+ protected Paint getPaint(Object value) {
+ if (value != null) {
+ return paintMap.get(value);
+ }
+ return null;
+ }
+
+ protected final String attributeName;
+
+ protected Map<Object, Paint> paintMap = new HashMap<Object, Paint>();
+}
diff --git a/src/jebl/gui/trees/treeviewer/decorators/BranchDecorator.java b/src/jebl/gui/trees/treeviewer/decorators/BranchDecorator.java
new file mode 100644
index 0000000..8ed87ec
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/decorators/BranchDecorator.java
@@ -0,0 +1,14 @@
+package jebl.gui.trees.treeviewer.decorators;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Tree;
+
+import java.awt.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: BranchDecorator.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public interface BranchDecorator {
+ Paint getBranchPaint(Tree tree, Node node);
+}
diff --git a/src/jebl/gui/trees/treeviewer/decorators/TaxonDecorator.java b/src/jebl/gui/trees/treeviewer/decorators/TaxonDecorator.java
new file mode 100644
index 0000000..1778a0d
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/decorators/TaxonDecorator.java
@@ -0,0 +1,14 @@
+package jebl.gui.trees.treeviewer.decorators;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.awt.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TaxonDecorator.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public interface TaxonDecorator {
+ Paint getTaxonPaint(Taxon taxon);
+ Font getTaxonFont(Taxon taxon, Font font);
+}
diff --git a/src/jebl/gui/trees/treeviewer/images/polarTree.png b/src/jebl/gui/trees/treeviewer/images/polarTree.png
new file mode 100644
index 0000000..c127e60
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer/images/polarTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer/images/radialTree.png b/src/jebl/gui/trees/treeviewer/images/radialTree.png
new file mode 100644
index 0000000..758e14c
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer/images/radialTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer/images/rectangularTree.png b/src/jebl/gui/trees/treeviewer/images/rectangularTree.png
new file mode 100644
index 0000000..c1ac499
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer/images/rectangularTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer/painters/AbstractPainter.java b/src/jebl/gui/trees/treeviewer/painters/AbstractPainter.java
new file mode 100644
index 0000000..9e9c9f6
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/painters/AbstractPainter.java
@@ -0,0 +1,25 @@
+package jebl.gui.trees.treeviewer.painters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: AbstractPainter.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public abstract class AbstractPainter<T> implements Painter<T> {
+ public void addPainterListener(PainterListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removePainterListener(PainterListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void firePainterChanged() {
+ for (PainterListener listener : listeners) {
+ listener.painterChanged();
+ }
+ }
+ private final List<PainterListener> listeners = new ArrayList<PainterListener>();
+}
diff --git a/src/jebl/gui/trees/treeviewer/painters/BasicLabelPainter.java b/src/jebl/gui/trees/treeviewer/painters/BasicLabelPainter.java
new file mode 100644
index 0000000..cbe5795
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/painters/BasicLabelPainter.java
@@ -0,0 +1,502 @@
+package jebl.gui.trees.treeviewer.painters;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.util.NumberFormatter;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+import java.util.List;
+import java.util.prefs.Preferences;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: BasicLabelPainter.java 705 2007-05-09 02:32:33Z stevensh $
+ */
+public class BasicLabelPainter extends AbstractPainter<Node> {
+
+ public static final String TAXON_NAMES = "Taxon Names";
+ public static final String NODE_HEIGHTS = "Node Heights";
+ public static final String BRANCH_LENGTHS = "Branch Lengths";
+
+
+ public BasicLabelPainter(String title, RootedTree tree, PainterIntent intent) {
+ this(title, tree, intent, 6);
+ }
+
+
+ public enum PainterIntent {
+ NODE,
+ BRANCH,
+ TIP
+ }
+
+ public BasicLabelPainter(String title, RootedTree tree, PainterIntent intent, int defaultSize) {
+ this.title = title;
+
+ this.defaultFontSize = defaultSize;
+ taxonLabelFont = new Font("sansserif", Font.PLAIN, defaultSize);
+
+ this.tree = tree;
+
+ hasNumericAttributes = false;
+
+ Set<String> names = new TreeSet<String>();
+
+ // by default, node properties are on nodes for rooted trees, on branches for unrooted trees
+ this.attribute = null;
+
+ List<String> sources = new ArrayList<String>();
+ boolean wantHeightsIfPossible = false;
+ boolean wantBranchesIfPossible = false;
+ boolean addNodeAttributes = false;
+ switch( intent ) {
+ case TIP: {
+ sources.add(TAXON_NAMES);
+ wantHeightsIfPossible = true;
+ for (Node node : tree.getExternalNodes() ) {
+ for(String s : node.getAttributeNames()){
+ if(!s.equalsIgnoreCase("size") && !s.equalsIgnoreCase("first residues"))
+ names.add(s);
+ }
+ //names.addAll(node.getAttributeNames());
+ }
+ break;
+ }
+ case NODE: {
+ wantHeightsIfPossible = true;
+ addNodeAttributes = !tree.conceptuallyUnrooted();
+ break;
+ }
+ case BRANCH: {
+ wantBranchesIfPossible = true;
+ addNodeAttributes = tree.conceptuallyUnrooted();
+ break;
+ }
+ }
+
+ if( addNodeAttributes ) {
+ for( Node node : tree.getInternalNodes() ) {
+ names.addAll(node.getAttributeNames());
+ }
+ }
+
+ if( wantHeightsIfPossible && tree.hasHeights() && !tree.conceptuallyUnrooted() ) {
+ sources.add(NODE_HEIGHTS);
+ hasNumericAttributes = true;
+ }
+
+ if( wantBranchesIfPossible && tree.hasLengths()) {
+ sources.add(BRANCH_LENGTHS);
+ hasNumericAttributes = true;
+ }
+
+ sources.addAll(names);
+
+ if (this.attribute == null && sources.size() > 0) {
+ this.attribute = sources.get(0);
+ } else {
+ this.attribute = "";
+ }
+
+ this.attributes = new String[sources.size()];
+ sources.toArray(this.attributes);
+
+ formatter = new NumberFormatter(4);
+ }
+
+ public void setTree(RootedTree tree) {
+ this.tree = tree;
+ }
+
+ protected String getLabel(Node node) {
+ String prefix = " ";
+ String suffix = " ";
+ if (attribute.equalsIgnoreCase(TAXON_NAMES)) {
+ return prefix+tree.getTaxon(node).getName()+suffix;
+ }
+
+ if( tree instanceof RootedTree ) {
+ final RootedTree rtree = (RootedTree) tree;
+
+ if (attribute.equalsIgnoreCase(NODE_HEIGHTS) ) {
+ return prefix+getFormattedValue(rtree.getHeight(node))+suffix;
+ } else if (attribute.equalsIgnoreCase(BRANCH_LENGTHS) ) {
+ return prefix+getFormattedValue(rtree.getLength(node))+suffix;
+ }
+ }
+
+ final Object value = node.getAttribute(attribute);
+ if (value != null) {
+ if (value instanceof Double) {
+ return prefix+formatter.getFormattedValue((Double) value)+suffix;
+ }
+ if(value instanceof Date){
+ DateFormat format = new SimpleDateFormat("dd MMM yyyy h:mm a");
+ return prefix+format.format((Date)value)+suffix;
+ }
+ String s = value.toString();
+ //limit node labels to 15 chars (plus ...)
+ //if(s.length() > 15)
+ // return s.substring(0,15)+"...";
+ return prefix+s+suffix;
+ }
+ return null;
+ }
+
+ private String getFormattedValue(double d){
+ if(d == 0)
+ return "0";
+ return formatter.getFormattedValue(d);
+ }
+
+ public float getFontSize() {
+ return defaultFontSize;
+ }
+
+ public float getFontMinSize() {
+ return defaultMinFontSize;
+ }
+
+ private float defaultFontSize;
+ private float defaultMinFontSize;
+ private int defaultDigits = 4;
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ private static final String isOPenKey = "_isopen";
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ firePainterChanged();
+ PREFS.putBoolean(getTitle() + isOPenKey, visible);
+ }
+
+// public void calibrate(Graphics2D g2, Node item) {
+// final Font oldFont = g2.getFont();
+// g2.setFont(taxonLabelFont);
+//
+// final FontMetrics fm = g2.getFontMetrics();
+// preferredHeight = fm.getHeight();
+// preferredWidth = 0;
+//
+// String label = getLabel(item);
+// if (label != null) {
+// Rectangle2D rect = fm.getStringBounds(label, g2);
+// preferredWidth = rect.getWidth();
+// }
+//
+// yOffset = (float)fm.getAscent();
+//
+// g2.setFont(oldFont);
+// }
+
+ public void calibrate(Graphics2D g2) {
+ final Font oldFont = g2.getFont();
+ g2.setFont(taxonLabelFont);
+
+ final FontMetrics fm = g2.getFontMetrics();
+ preferredHeight = fm.getHeight();
+
+ yOffset = (float)fm.getAscent();
+
+ g2.setFont(oldFont);
+ }
+
+ public double getWidth(Graphics2D g2, Node item) {
+ final String label = getLabel(item);
+ if( label != null ) {
+ final Font oldFont = g2.getFont();
+ g2.setFont(taxonLabelFont);
+
+ final FontMetrics fm = g2.getFontMetrics();
+ Rectangle2D rect = fm.getStringBounds(label, g2);
+ g2.setFont(oldFont);
+ return rect.getWidth();
+ }
+
+ return 0.0;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight + yOffset;
+ }
+
+ public boolean setFontSize(float size, boolean fire) {
+ if( defaultFontSize != size ) {
+ taxonLabelFont = taxonLabelFont.deriveFont(size);
+ defaultFontSize = size;
+ if( fire ) firePainterChanged();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean setFontMinSize(float fontsize, boolean fire) {
+ if( defaultMinFontSize != fontsize ) {
+ defaultMinFontSize = fontsize;
+ if( fire ) firePainterChanged();
+ return true;
+ }
+ return false;
+ }
+
+ private void setSignificantDigits(int digits) {
+ assert formatter != null;
+
+ formatter.setSignificantFigures(digits);
+ firePainterChanged();
+ }
+
+ public void setForeground(Paint foreground) {
+ this.foreground = foreground;
+ firePainterChanged();
+ }
+
+ public void setBackground(Paint background) {
+ this.background = background;
+ firePainterChanged();
+ }
+
+ public void setBorder(Paint borderPaint, Stroke borderStroke) {
+ this.borderPaint = borderPaint;
+ this.borderStroke = borderStroke;
+ firePainterChanged();
+ }
+
+ public void paint(Graphics2D g2, Node item, Justification justification, Rectangle2D bounds) {
+ final Font oldFont = g2.getFont();
+
+ if (background != null) {
+ g2.setPaint(background);
+ g2.fill(bounds);
+ }
+
+ if (borderPaint != null && borderStroke != null) {
+ g2.setPaint(borderPaint);
+ g2.setStroke(borderStroke);
+ g2.draw(bounds);
+ }
+
+ g2.setPaint(foreground);
+ g2.setFont(taxonLabelFont);
+
+ final String label = getLabel(item);
+ if (label != null) {
+
+ Rectangle2D rect = g2.getFontMetrics().getStringBounds(label, g2);
+
+ float xOffset = 0;
+ float y = yOffset + (float) bounds.getY();
+ switch (justification) {
+ case CENTER:
+ //xOffset = (float)(-rect.getWidth()/2.0);
+ //y = yOffset + (float) rect.getY();
+ // y = (float)bounds.getHeight()/2;
+ //xOffset = (float) (bounds.getX() + (bounds.getWidth() - rect.getWidth()) / 2.0);
+ break;
+ case FLUSH:
+ case LEFT:
+ xOffset = (float) bounds.getX();
+ break;
+ case RIGHT:
+ xOffset = (float) (bounds.getX() + bounds.getWidth() - rect.getWidth());
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alignment enum option");
+ }
+
+ g2.drawString(label, xOffset, y);
+ //g2.draw(bounds);
+ }
+
+ g2.setFont(oldFont);
+ }
+
+ public String[] getAttributes() {
+ return attributes;
+ }
+
+ public void setAttribute(String attribute) {
+ this.attribute = attribute;
+ firePainterChanged();
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // nothing to do
+ }
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(BasicLabelPainter.class);
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ final JCheckBox showTextCHeckBox = new JCheckBox("Show " + getTitle());
+ if (! detachPrimaryCheckbox) {
+ optionsPanel.addComponent(showTextCHeckBox);
+ }
+
+ visible = PREFS.getBoolean(getTitle() + isOPenKey, isVisible());
+ showTextCHeckBox.setSelected(visible);
+
+ showTextCHeckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean selected = showTextCHeckBox.isSelected();
+ if( isVisible() != selected ) {
+ setVisible(selected);
+ }
+ }
+ });
+
+ final String whatPrefKey = getTitle() + "_whatToDisplay";
+ String[] attributes = getAttributes();
+ final JComboBox combo1 = new JComboBox(attributes);
+ combo1.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String) combo1.getSelectedItem();
+ setAttribute(attribute);
+ PREFS.put(whatPrefKey, attribute);
+ }
+ });
+
+ final String whatToDisplay = PREFS.get(whatPrefKey, null);
+ if( whatToDisplay != null ) {
+ int i = Arrays.asList(attributes).indexOf(whatToDisplay);
+ if( i >= 0 ) {
+ combo1.setSelectedIndex(i);
+ }
+ }
+
+ optionsPanel.addComponentWithLabel("Display:", combo1);
+ final JSpinner fontSizeSpinner = new JSpinner(new SpinnerNumberModel(defaultFontSize, 0.01, 48, 1));
+
+ optionsPanel.addComponentWithLabel("Font Size:", fontSizeSpinner);
+ //final boolean xselected = showTextCHeckBox.isSelected();
+ //label1.setEnabled(selected);
+ //fontSizeSpinner.setEnabled(selected);
+
+ final String fontSizePrefKey = getTitle() + "_fontsize";
+ final float fontsize = PREFS.getFloat(fontSizePrefKey, taxonLabelFont.getSize());
+ setFontSize(fontsize, false);
+ fontSizeSpinner.setValue(fontsize);
+
+ fontSizeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final float size = ((Double) fontSizeSpinner.getValue()).floatValue();
+ setFontSize(size, true);
+ PREFS.putFloat(fontSizePrefKey, size);
+ }
+ });
+
+
+ //-----------------------------------------
+
+
+
+ //final boolean xselected = showTextCHeckBox.isSelected();
+ //label1.setEnabled(selected);
+ //fontSizeSpinner.setEnabled(selected);
+
+ final String fontMinSizePrefKey = getTitle() + "_fontminsize";
+ final float size = PREFS.getFloat(fontMinSizePrefKey, 6);
+ setFontMinSize(size, false);
+
+ final JSpinner fontMinSizeSpinner = new JSpinner(new SpinnerNumberModel(defaultMinFontSize, 0.01, 48, 1));
+ optionsPanel.addComponentWithLabel("Minimum Size:", fontMinSizeSpinner);
+ //fontMinSizeSpinner.setValue(size);
+
+ fontMinSizeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final float size = ((Double) fontMinSizeSpinner.getValue()).floatValue();
+ setFontMinSize(size, true);
+ PREFS.putFloat(fontMinSizePrefKey, size);
+ }
+ });
+ //-------------------------
+ final JSpinner digitsSpinner = new JSpinner(new SpinnerNumberModel(defaultDigits, 2, 14, 1));
+
+ if( hasNumericAttributes ) {
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Significant Digits:", digitsSpinner);
+ // label2.setEnabled(selected);
+ // digitsSpinner.setEnabled(selected);
+
+ final String digitsPrefKey = getTitle() + "_sigDigits";
+ final int digits = PREFS.getInt(digitsPrefKey, defaultDigits);
+ setSignificantDigits(digits);
+ digitsSpinner.setValue(digits);
+
+ digitsSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int digits = (Integer)digitsSpinner.getValue();
+ setSignificantDigits(digits);
+ PREFS.putInt(digitsPrefKey, digits);
+ }
+ });
+ }
+
+ controls = new Controls(getTitle(), optionsPanel, false, false, detachPrimaryCheckbox ? showTextCHeckBox : null);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+
+ public String getTitle() {
+ return title;
+ }
+
+ private final String title;
+
+ private Paint foreground = Color.BLACK;
+ private Paint background = null;
+ private Paint borderPaint = null;
+ private Stroke borderStroke = null;
+
+ private Font taxonLabelFont;
+ //private double preferredWidth;
+ private double preferredHeight;
+ private float yOffset;
+
+ private boolean visible = true;
+
+ private NumberFormatter formatter = null;
+ private boolean hasNumericAttributes = false;
+
+ private Tree tree;
+ protected String attribute;
+ protected String[] attributes;
+}
diff --git a/src/jebl/gui/trees/treeviewer/painters/Painter.java b/src/jebl/gui/trees/treeviewer/painters/Painter.java
new file mode 100644
index 0000000..de7e2de
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/painters/Painter.java
@@ -0,0 +1,46 @@
+package jebl.gui.trees.treeviewer.painters;
+
+import org.virion.jam.controlpanels.ControlsProvider;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+
+/**
+ * A painter draws a particular decoration onto the tree within a
+ * rectangle.
+ * @author Andrew Rambaut
+ * @version $Id: Painter.java 595 2006-12-27 20:21:41Z pepster $
+ */
+public interface Painter<T> extends ControlsProvider {
+
+ public enum Orientation {
+ TOP,
+ LEFT,
+ BOTTOM,
+ RIGHT
+ }
+
+ public enum Justification {
+ FLUSH,
+ LEFT,
+ RIGHT,
+ CENTER
+ }
+
+ boolean isVisible();
+
+ //void calibrate(Graphics2D g2, T item);
+
+ void calibrate(Graphics2D g2);
+
+ // May change paint and stroke
+ void paint(Graphics2D g2, T item, Justification justification, Rectangle2D bounds);
+
+ double getWidth(Graphics2D g2, T item);
+ double getPreferredHeight();
+ double getHeightBound();
+
+ void addPainterListener(PainterListener listener);
+ void removePainterListener(PainterListener listener);
+}
diff --git a/src/jebl/gui/trees/treeviewer/painters/PainterListener.java b/src/jebl/gui/trees/treeviewer/painters/PainterListener.java
new file mode 100644
index 0000000..15fba43
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/painters/PainterListener.java
@@ -0,0 +1,11 @@
+package jebl.gui.trees.treeviewer.painters;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PainterListener.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public interface PainterListener {
+
+ void painterChanged();
+
+}
diff --git a/src/jebl/gui/trees/treeviewer/painters/ScaleBarPainter.java b/src/jebl/gui/trees/treeviewer/painters/ScaleBarPainter.java
new file mode 100644
index 0000000..bc67006
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/painters/ScaleBarPainter.java
@@ -0,0 +1,338 @@
+package jebl.gui.trees.treeviewer.painters;
+
+import jebl.gui.trees.treeviewer.TreePane;
+import jebl.gui.trees.treeviewer.TreeViewer;
+import org.virion.jam.components.RealNumberField;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ScaleBarPainter.java 674 2007-03-28 05:23:26Z stevensh $
+ */
+public class ScaleBarPainter extends AbstractPainter<TreePane> {
+ private int defaultFontSize;
+ private double scaleRange;
+ private double userScaleRange = 0.0;
+
+ public ScaleBarPainter() {
+ this(0.0, 12);
+ }
+
+ public ScaleBarPainter(double scaleRange) {
+ this(scaleRange, 12);
+ }
+
+ public ScaleBarPainter(int defaultSize) {
+ this(0.0, defaultSize);
+ }
+
+ public ScaleBarPainter(double scaleRange, int defaultSize) {
+ this.scaleRange = scaleRange;
+ this.defaultFontSize = defaultSize;
+ scaleFont = new Font("sansserif", Font.PLAIN, defaultFontSize);
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ firePainterChanged();
+ }
+
+ public void calibrate(Graphics2D g2) {
+ Font oldFont = g2.getFont();
+ g2.setFont(scaleFont);
+
+ FontMetrics fm = g2.getFontMetrics();
+
+// if( userScaleRange != 0.0 ) {
+// scaleRange = userScaleRange;
+// } else {
+// final double treeScale = treePane.getTreeScale();
+// if( treeScale == 0.0 ) {
+// scaleRange = 0.0;
+// } else {
+// int w10 = treePane.getWidth() / 10;
+//
+// double low = w10 /treeScale;
+// double b = -(Math.ceil(Math.log10(low)) - 1);
+// for(int n = 0; n < 3; ++n) {
+// double factor = Math.pow(10, b);
+// double x = ((int)(low * factor) + 1)/factor;
+// if( n == 2 || x < w10 * 2 ) {
+// scaleRange = x;
+// break;
+// }
+// ++b;
+// }
+// }
+// }
+
+ final double labelHeight = fm.getHeight();
+
+// preferredWidth = treePane.getTreeScale() * scaleRange;
+ preferredHeight = labelHeight + 4 + scaleBarStroke.getLineWidth();
+
+ yOffset = (float) (fm.getAscent()) + 4 + scaleBarStroke.getLineWidth();
+
+ g2.setFont(oldFont);
+ }
+
+ public void paint(Graphics2D g2, TreePane treePane, Justification justification, Rectangle2D bounds) {
+ Font oldFont = g2.getFont();
+ Paint oldPaint = g2.getPaint();
+ Stroke oldStroke = g2.getStroke();
+
+ if (background != null) {
+ g2.setPaint(background);
+ g2.fill(bounds);
+ }
+
+ if (borderPaint != null && borderStroke != null) {
+ g2.setPaint(borderPaint);
+ g2.setStroke(borderStroke);
+ g2.draw(bounds);
+ }
+
+ g2.setFont(scaleFont);
+
+ // sets scale range
+ final double preferredWidth = getWidth(g2, treePane);
+
+ // we don't need accuracy but a nice short number
+ final String label = Double.toString(scaleRange);
+
+ Rectangle2D rect = g2.getFontMetrics().getStringBounds(label, g2);
+
+ double x1, x2;
+ float xOffset;
+ switch (justification) {
+ case CENTER:
+ xOffset = (float) (bounds.getX() + (bounds.getWidth() - rect.getWidth()) / 2.0);
+
+ x1 = (bounds.getX() + (bounds.getWidth() - preferredWidth) / 2.0);
+ x2 = x1 + preferredWidth;
+ break;
+ case FLUSH:
+ case LEFT:
+ xOffset = (float) bounds.getX();
+ x1 = bounds.getX();
+ x2 = x1 + preferredWidth;
+ break;
+ case RIGHT:
+ xOffset = (float) (bounds.getX() + bounds.getWidth() - rect.getWidth());
+ x2 = bounds.getX() + bounds.getWidth();
+ x1 = x2 - preferredWidth;
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alignment enum option");
+ }
+
+ g2.setPaint(foreground);
+ g2.setStroke(scaleBarStroke);
+
+ g2.draw(new Line2D.Double(x1, bounds.getY(), x2, bounds.getY()));
+
+ g2.drawString(label, xOffset, yOffset + (float) bounds.getY());
+
+ g2.setFont(oldFont);
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+ }
+
+ public double getWidth(Graphics2D g2, TreePane treePane) {
+ Font oldFont = g2.getFont();
+ g2.setFont(scaleFont);
+
+ FontMetrics fm = g2.getFontMetrics();
+
+ if( userScaleRange != 0.0 ) {
+ scaleRange = userScaleRange;
+ } else {
+ final double treeScale = treePane.getTreeScale();
+ if( treeScale == 0.0 ) {
+ scaleRange = 0.0;
+ } else {
+ int w10 = treePane.getWidth() / 10;
+
+ double low = w10 /treeScale;
+ double b = -(Math.ceil(Math.log10(low)) - 1);
+ for(int n = 0; n < 3; ++n) {
+ double factor = Math.pow(10, b);
+ double x = ((int)(low * factor) + 1)/factor;
+ if( n == 2 || x < w10 * 2 ) {
+ scaleRange = x;
+ break;
+ }
+ ++b;
+ }
+ }
+ }
+
+ double preferredWidth = treePane.getTreeScale() * scaleRange;
+ g2.setFont(oldFont);
+ return preferredWidth;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight + yOffset;
+ }
+
+ public void setScaleRange(double scaleRange) {
+ this.userScaleRange = scaleRange;
+ firePainterChanged();
+ }
+
+ public void setFontSize(float size) {
+ scaleFont = scaleFont.deriveFont(size);
+ firePainterChanged();
+ }
+
+ public void setForeground(Paint foreground) {
+ this.foreground = foreground;
+ firePainterChanged();
+ }
+
+ public void setBackground(Paint background) {
+ this.background = background;
+ firePainterChanged();
+ }
+
+ public void setBorder(Paint borderPaint, Stroke borderStroke) {
+ this.borderPaint = borderPaint;
+ this.borderStroke = borderStroke;
+ firePainterChanged();
+ }
+
+ public void setLineWeight(float weight) {
+ this.scaleBarStroke = new BasicStroke(weight, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+ firePainterChanged();
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // nothing to do
+ }
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+ final Preferences PREFS = Preferences.userNodeForPackage(TreeViewer.class);
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ final JCheckBox showScaleBarCB = new JCheckBox("Show Scale Bar");
+ if (! detachPrimaryCheckbox) {
+ optionsPanel.addComponent(showScaleBarCB);
+ }
+
+ showScaleBarCB.setSelected(isVisible());
+
+ final RealNumberField text1 = new RealNumberField(0.0, Double.MAX_VALUE);
+ text1.setValue(scaleRange);
+
+ text1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ Double value = text1.getValue();
+ if (value != null) {
+ setScaleRange(value);
+ PREFS.putDouble("scalebar_scaleRange",value);
+ }
+ }
+ });
+ text1.setText(PREFS.getDouble("scalebar_scaleRange",0.0));
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Scale Range:", text1, true);
+
+ final JSpinner spinner1 = new JSpinner(new SpinnerNumberModel(defaultFontSize, 0.01, 48.0, 1.0));
+
+ spinner1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setFontSize(((Double) spinner1.getValue()).floatValue());
+ PREFS.putDouble("scalebar_fontSize",(Double)spinner1.getValue());
+ }
+ });
+ spinner1.setValue(PREFS.getDouble("scalebar_fontSize",defaultFontSize));
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Font Size:", spinner1);
+
+ final JSpinner spinner2 = new JSpinner(new SpinnerNumberModel(1.0, 0.01, 48.0, 1.0));
+
+ spinner2.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setLineWeight(((Double) spinner2.getValue()).floatValue());
+ PREFS.putDouble("scalebar_lineWeight",(Double)spinner2.getValue());
+ }
+ });
+ spinner2.setValue(PREFS.getDouble("scalebar_lineWeight",1));
+ final JLabel label3 = optionsPanel.addComponentWithLabel("Line Weight:", spinner2);
+
+ final boolean isSelected = showScaleBarCB.isSelected();
+ label1.setEnabled(isSelected);
+ text1.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ spinner1.setEnabled(isSelected);
+ label3.setEnabled(isSelected);
+ spinner2.setEnabled(isSelected);
+
+ showScaleBarCB.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean isSelected = showScaleBarCB.isSelected();
+ label1.setEnabled(isSelected);
+ text1.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ spinner1.setEnabled(isSelected);
+ label3.setEnabled(isSelected);
+ spinner2.setEnabled(isSelected);
+
+ setVisible(isSelected);
+ }
+ });
+
+ controls = new Controls("Scale Bar", optionsPanel, false, false, detachPrimaryCheckbox ? showScaleBarCB : null);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+
+ private boolean visible = true;
+
+ private Paint foreground = Color.BLACK;
+ private Paint background = null;
+ private Paint borderPaint = null;
+ private Stroke borderStroke = null;
+ private BasicStroke scaleBarStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+
+ private Font scaleFont;
+ private double preferredHeight;
+ //private double preferredWidth;
+
+ private float yOffset;
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/AbstractTreeLayout.java b/src/jebl/gui/trees/treeviewer/treelayouts/AbstractTreeLayout.java
new file mode 100644
index 0000000..d7a8834
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/AbstractTreeLayout.java
@@ -0,0 +1,93 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: AbstractTreeLayout.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public abstract class AbstractTreeLayout implements TreeLayout {
+ public void setTree(Tree tree) {
+ this.tree = (RootedTree)tree;
+ invalidate();
+ }
+
+ public void invalidate() {
+ invalid = true;
+ fireTreeLayoutChanged();
+ }
+
+ public Point2D getNodePoint(Node node) {
+ checkValidation();
+ return nodePoints.get(node);
+ }
+
+ public Shape getBranchPath(Node node) {
+ checkValidation();
+ return branchPaths.get(node);
+ }
+
+ public Line2D getTaxonLabelPath(Node node) {
+ checkValidation();
+ return taxonLabelPaths.get(node);
+ }
+
+ public Line2D getBranchLabelPath(Node node) {
+ checkValidation();
+ return branchLabelPaths.get(node);
+ }
+
+ public Line2D getNodeLabelPath(Node node) {
+ checkValidation();
+ return nodeLabelPaths.get(node);
+ }
+
+ public Shape getCalloutPath(Node node) {
+ checkValidation();
+ return calloutPaths.get(node);
+ }
+
+ private void checkValidation() {
+ if (invalid) {
+ validate();
+ invalid = false;
+ }
+ }
+
+ public void addTreeLayoutListener(TreeLayoutListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTreeLayoutListener(TreeLayoutListener listener) {
+ listeners.remove(listener);
+ }
+
+ protected void fireTreeLayoutChanged() {
+ for (TreeLayoutListener listener : listeners) {
+ listener.treeLayoutChanged();
+ }
+ }
+
+ protected abstract void validate();
+
+ private boolean invalid = true;
+ protected RootedTree tree = null;
+ protected Map<Node, Point2D> nodePoints = new HashMap<Node, Point2D>();
+ protected Map<Node, Shape> branchPaths = new HashMap<Node, Shape>();
+ protected Map<Node, Line2D> taxonLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Line2D> branchLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Line2D> nodeLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Shape> calloutPaths = new HashMap<Node, Shape>();
+
+ private Set<TreeLayoutListener> listeners = new HashSet<TreeLayoutListener>();
+}
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/PolarTreeLayout.java b/src/jebl/gui/trees/treeviewer/treelayouts/PolarTreeLayout.java
new file mode 100644
index 0000000..165d977
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/PolarTreeLayout.java
@@ -0,0 +1,356 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PolarTreeLayout.java 685 2007-04-10 00:17:55Z stevensh $
+ */
+public class PolarTreeLayout extends AbstractTreeLayout {
+
+ public enum TaxonLabelPosition {
+ FLUSH,
+ RADIAL,
+ HORIZONTAL
+ }
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public boolean maintainAspectRatio() {
+ return true;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Line2D getHeightLine(double height) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public boolean alignTaxa() {
+ return false;
+ }
+
+ public Shape getCollapsedNode(Node node, double ratio) {
+ final Point2D nodePoint = getNodePoint(node);
+ return new Line2D.Double(nodePoint.getX(), nodePoint.getY(), nodePoint.getX() + .01, nodePoint.getY());
+ }
+
+ public boolean smallSubTree(Node node, AffineTransform transform) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public int getNodeMarkerRadiusUpperLimit(Node node, AffineTransform transform) {
+ return -1; // todo
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // do nothing...
+ }
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ if( ! tree.conceptuallyUnrooted() ) {
+ final JSlider slider1 = new JSlider(SwingConstants.HORIZONTAL, 0, 3600, 0);
+ slider1.setValue((int) (180.0 - (rootAngle * 10)));
+ slider1.setPaintTicks(true);
+ slider1.setPaintLabels(true);
+
+ slider1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = 180 + (slider1.getValue() / 10.0);
+ setRootAngle(value % 360);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Angle:", slider1, true);
+
+ final JSlider slider2 = new JSlider(SwingConstants.HORIZONTAL, 0, 10000, 0);
+ slider2.setValue((int) (rootLength * 10000));
+ slider2.setPaintTicks(true);
+ slider2.setPaintLabels(true);
+
+ slider2.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = slider2.getValue();
+ setRootLength(value / 10000.0);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Length:", slider2, true);
+ }
+
+ final JSlider slider3 = new JSlider(SwingConstants.HORIZONTAL, 0, 3600, 0);
+ slider3.setValue((int) (360.0 - (angularRange * 10)));
+ slider3.setPaintTicks(true);
+ slider3.setPaintLabels(true);
+
+ slider3.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = 360.0 - (slider3.getValue() / 10.0);
+ setAngularRange(value);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Angle Range:", slider3, true);
+//in Geneious 2.5.4 this option did nothing, and in 3.0 it caused a crash. I am therefore removing
+//this option until we can figure out what it does and fix the crash.
+// final JComboBox combo1 = new JComboBox();
+// for (TaxonLabelPosition position : TaxonLabelPosition.values()) {
+// if (position != TaxonLabelPosition.HORIZONTAL) // not implemented yet
+// combo1.addItem(position);
+// }
+// combo1.addItemListener(new ItemListener() {
+// public void itemStateChanged(ItemEvent itemEvent) {
+// setTaxonLabelPosition((TaxonLabelPosition) combo1.getSelectedItem());
+//
+// }
+// });
+ //optionsPanel.addComponentWithLabel("Label Position:", combo1);
+
+ controls = new Controls("Layout", optionsPanel, true);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+
+ public void setRootAngle(double rootAngle) {
+ this.rootAngle = rootAngle;
+ invalidate();
+ }
+
+ public void setRootLength(double rootLength) {
+ this.rootLength = rootLength;
+ invalidate();
+ }
+
+ public void setAngularRange(double angularRange) {
+ this.angularRange = angularRange;
+ invalidate();
+ }
+
+ public void setTaxonLabelPosition(TaxonLabelPosition taxonLabelPosition) {
+ this.taxonLabelPosition = taxonLabelPosition;
+ invalidate();
+ }
+
+ protected void validate() {
+ nodePoints.clear();
+ branchPaths.clear();
+ taxonLabelPaths.clear();
+ calloutPaths.clear();
+
+ final Node root = this.tree.getRootNode();
+ final double rl = (rootLength * this.tree.getHeight(root)) * 10.0;
+
+ maxXPosition = 0.0;
+ getMaxXPosition(root, rl);
+
+ yPosition = 0.0;
+ yIncrement = 1.0 / tree.getExternalNodes().size();
+
+ final Point2D rootPoint = constructNode(root, rl);
+
+ // construct a root branch line
+ final double y = rootPoint.getY();
+ Line2D line = new Line2D.Double(transform(0.0, y), transform(rootPoint.getX(), y));
+
+ // add the line to the map of branch paths
+ branchPaths.put(root, line);
+ }
+
+ private Point2D constructNode(Node node, double xPosition) {
+
+ Point2D nodePoint;
+ Point2D transformedNodePoint;
+
+ if (!tree.isExternal(node)) {
+
+ double yPos = 0.0;
+
+ final List<Node> children = tree.getChildren(node);
+ final Point2D[] childPoints = new Point2D[children.size()];
+
+ int i = 0;
+ for (Node child : children) {
+
+ final double length = tree.getLength(child);
+ childPoints[i] = constructNode(child, xPosition + length);
+ yPos += childPoints[i].getY();
+
+ i++;
+ }
+
+ // the y-position of the node is the average of the child nodes
+ yPos /= children.size();
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+ transformedNodePoint = transform(nodePoint);
+
+ final double start = getAngle(yPos);
+
+ i = 0;
+ for (Node child : children) {
+
+ GeneralPath branchPath = new GeneralPath();
+
+ final double childY = childPoints[i].getY();
+
+ final double finish = getAngle(childY);
+
+ Arc2D arc = new Arc2D.Double();
+ arc.setArcByCenter(0.0, 0.0, nodePoint.getX(), start, finish - start, Arc2D.OPEN);
+ branchPath.append(arc, true);
+
+ final Point2D p = transform(childPoints[i]);
+ branchPath.lineTo((float) p.getX(), (float) p.getY());
+
+ // add the branchPath to the map of branch paths
+ branchPaths.put(child, branchPath);
+
+ final double x3 = (nodePoint.getX() + childPoints[i].getX()) / 2;
+
+ Line2D branchLabelPath = new Line2D.Double(transform(x3 - 1.0, childY), transform(x3 + 1.0, childY));
+
+ branchLabelPaths.put(child, branchLabelPath);
+
+ i++;
+ }
+
+ // Line2D nodeLabelPath = new Line2D.Double(transform(maxXPosition, yPos), transform(maxXPosition + 1.0, yPos));
+ Line2D nodeLabelPath = new Line2D.Double(transform(nodePoint.getX(), yPos), transform(nodePoint.getX()+ 1.0, yPos));
+
+ nodeLabelPaths.put(node, nodeLabelPath);
+ } else {
+
+ nodePoint = new Point2D.Double(xPosition, yPosition);
+ transformedNodePoint = transform(nodePoint);
+
+ Line2D taxonLabelPath;
+
+ if (taxonLabelPosition == TaxonLabelPosition.FLUSH) {
+
+ taxonLabelPath = new Line2D.Double(transformedNodePoint, transform(xPosition + 1.0, yPosition));
+
+ } else if (taxonLabelPosition == TaxonLabelPosition.RADIAL) {
+
+ taxonLabelPath = new Line2D.Double(transform(maxXPosition, yPosition),
+ transform(maxXPosition + 1.0, yPosition));
+
+ Line2D calloutPath = new Line2D.Double(transformedNodePoint, transform(maxXPosition, yPosition));
+
+ calloutPaths.put(node, calloutPath);
+
+ } else if (taxonLabelPosition == TaxonLabelPosition.HORIZONTAL) {
+ // this option disabled in getControls (JH)
+ throw new UnsupportedOperationException("Not implemented yet");
+ } else {
+ // this is a bug
+ throw new IllegalArgumentException("Unrecognized enum value");
+ }
+
+ taxonLabelPaths.put(node, taxonLabelPath);
+
+ yPosition += yIncrement;
+ }
+
+ // add the node point to the map of node points
+ nodePoints.put(node, transformedNodePoint);
+
+ return nodePoint;
+ }
+
+ private void getMaxXPosition(Node node, double xPosition) {
+
+ if (!tree.isExternal(node)) {
+
+ List<Node> children = tree.getChildren(node);
+
+ for (Node child : children) {
+ final double length = tree.getLength(child);
+ getMaxXPosition(child, xPosition + length);
+ }
+
+ } else {
+ if (xPosition > maxXPosition) {
+ maxXPosition = xPosition;
+ }
+ }
+ }
+
+ /**
+ * Polar transform
+ *
+ * @param point
+ * @return the point in polar space
+ */
+ private Point2D transform(Point2D point) {
+ return transform(point.getX(), point.getY());
+ }
+
+ /**
+ * Polar transform
+ *
+ * @param x
+ * @param y
+ * @return the point in polar space
+ */
+ private Point2D transform(double x, double y) {
+ double r = - Math.toRadians(getAngle(y));
+ double tx = x * Math.cos(r);
+ double ty = x * Math.sin(r);
+ return new Point2D.Double(tx, ty);
+ }
+
+ private double getAngle(double y) {
+ return rootAngle - ((360.0 - angularRange) * 0.5) - (y * angularRange);
+ }
+
+ private double yPosition;
+ private double yIncrement;
+
+ private double maxXPosition;
+
+ private double rootAngle = 180.0;
+ private double rootLength = 0.01;
+ private double angularRange = 360.0;
+
+ private TaxonLabelPosition taxonLabelPosition = TaxonLabelPosition.FLUSH;
+}
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/RadialTreeLayout.java b/src/jebl/gui/trees/treeviewer/treelayouts/RadialTreeLayout.java
new file mode 100644
index 0000000..a356f1a
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/RadialTreeLayout.java
@@ -0,0 +1,366 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Utils;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RadialTreeLayout.java 612 2007-01-08 03:50:20Z pepster $
+ */
+public class RadialTreeLayout extends AbstractTreeLayout {
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public boolean maintainAspectRatio() {
+ return true;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Line2D getHeightLine(double height) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public boolean alignTaxa() {
+ return false;
+ }
+
+ public Shape getCollapsedNode(Node node, double ratio) {
+ Node first = node;
+ while( ! tree.isExternal(first) ) {
+ first = tree.getChildren(first).get(0);
+ }
+ Node last = node;
+ while( ! tree.isExternal(last) ) {
+ final List<Node> children = tree.getChildren(last);
+ last = children.get(children.size()- 1);
+ }
+
+ final Point2D n = getNodePoint(node);
+
+ double maxDistanceOfTaxaFromNode = 0.0;
+ for( Node n1 : Utils.getNodes(tree, node) ) {
+ if( tree.isExternal(n1) ) {
+ final Point2D d = getNodePoint(n1);
+
+ final double v = d.distanceSq(n);
+ maxDistanceOfTaxaFromNode = Math.max(v, maxDistanceOfTaxaFromNode);
+ }
+ }
+ maxDistanceOfTaxaFromNode = Math.sqrt(maxDistanceOfTaxaFromNode);
+
+ final Point2D c1 = getNodePoint(first);
+ final Point2D cn = getNodePoint(last);
+
+ // Origin of graphic system is top left, with Y axis flowing the wrong way, while atan2 and
+ // arc drawing follow the conventional way - henece dy's need to be reversed
+ final double dy = -(cn.getY() - n.getY());
+ final double dx = (cn.getX() - n.getX());
+ final double angle1 = Math.atan2(dy, dx);
+
+ final double dy1 = -(c1.getY() - n.getY());
+ final double dx1 = (c1.getX() - n.getX());
+ final double angle2 = Math.atan2(dy1, dx1);
+
+ final Arc2D.Float arc = new Arc2D.Float();
+ final double radToDeg = (180 / Math.PI);
+
+ double arcStartAngle = angle1 * radToDeg;
+ if( arcStartAngle < 0 ) arcStartAngle += 360;
+
+ double angExt = angle2 - angle1;
+ if( angExt < 0 ) angExt += 2*Math.PI;
+ double arcSpan = radToDeg * angExt;
+
+// System.out.println("first (to) " + tree.getTaxon(first).getName() + " last (from) " + tree.getTaxon(last).getName());
+// System.out.println(" angle1 " + angle1 * (180/Math.PI) + " angle2 " + angle2 * (180/Math.PI));
+// System.out.println(" from " + st + " ext " + angExt1 + " end " + en);
+// System.out.println(" dx last " + dx + " " + dy + " dx first " + dx1 + " " + dy1);
+
+ final double radius = maxDistanceOfTaxaFromNode * ratio;
+ arc.setArcByCenter(n.getX(), n.getY(), radius, arcStartAngle, arcSpan, Arc2D.PIE);
+
+ return arc;
+ }
+
+ private double distToNode( Point2D nodeLoc, Node to, AffineTransform transform) {
+ final Point2D parentLoc = getNodePoint(to);
+ final double dx = (parentLoc.getX() - nodeLoc.getX()) * transform.getScaleX();
+ final double dy = (parentLoc.getY() - nodeLoc.getY()) * transform.getScaleY();
+ return dx*dx + dy *dy;
+ }
+
+ public int getNodeMarkerRadiusUpperLimit(Node node, AffineTransform transform) {
+ final Node parent = tree.getParent(node);
+ double d = Double.MAX_VALUE;
+ final Point2D nodeLoc = getNodePoint(node);
+ if( parent != null) {
+ d = distToNode(nodeLoc, parent, transform);
+ }
+ final List<Node> nodes = tree.getChildren(node);
+ for( Node n : nodes ) {
+ final double dc = distToNode(nodeLoc, n, transform);
+
+ d = Math.min(dc, d); assert d >= 0;
+ }
+
+ return (int)(Math.sqrt(d) * 0.33);
+ }
+
+ public boolean smallSubTree(Node node, AffineTransform transform) {
+ final List<Node> children = tree.getChildren(node);
+ if( children.size() < 2 ) return false;
+
+ int method = 0;
+ if( method == 0 ) {
+ Point2D[] tc = new Point2D.Double[2];
+ int[] indices = {0, children.size()-1};
+ for(int nc = 0; nc < 2; ++nc) {
+ tc[nc] = new Point2D.Double();
+ transform.transform(nodePoints.get(children.get(indices[nc])), tc[nc]);
+ }
+
+ return tc[0].distanceSq(tc[1]) < 5 * 5;
+ }
+
+ if( method == 1 ) {
+ Point2D[] tc = new Point2D.Double[children.size()];
+ for(int nc = 0; nc < children.size(); ++nc) {
+ tc[nc] = new Point2D.Double();
+ transform.transform(nodePoints.get(children.get(nc)), tc[nc]);
+ }
+
+ for(int nc = 0; nc < children.size(); ++nc) {
+ int[] dirs = {-1, +1};
+ for(int d : dirs ) {
+ int k = d + nc;
+ if( 0 <= k && k < children.size() ) {
+ if( tc[nc].distanceSq(tc[k]) < 5*5 ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+// int nShortest = 0;
+// double shortest = tree.getLength(children.get(nShortest));
+// for(int nc = 1; nc < children.size(); ++nc) {
+// final double length = tree.getLength(children.get(nc));
+// if( length < shortest ) {
+// shortest = length;
+// nShortest = nc;
+// }
+// }
+//
+// Point2D origin = new Point2D.Double();
+// transform.transform(new Point2D.Double(0, 0), origin);
+//
+// final Point2D location = nodePoints.get(node);
+// final Point2D slocation = nodePoints.get(children.get(nShortest));
+// int[] dirs = {-1, +1};
+// for(int d : dirs ) {
+// int k = d + nShortest;
+// if( 0 <= k && k < children.size() ) {
+// final Point2D d1 = nodePoints.get(children.get(k));
+// final Line2D l = new Line2D.Double(location, d1);
+// final double v = l.ptLineDist(slocation);
+//
+// Point2D dst = new Point2D.Double();
+// transform.transform(new Point2D.Double(0, v), dst);
+//
+// if( dst.distanceSq(origin) < 5*5 ) {
+// return true;
+// }
+//// final double x = dst.getX();
+//// final double y = dst.getY();
+//// if( x*x + y*y < 5*5 ) {
+//// return true;
+//// }
+// }
+// }
+
+
+ return false;
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // do nothing...
+ }
+
+ // radians
+ private double rootAngle = 0.0;
+ public void setRootAngle(double angleInDeg) {
+ this.rootAngle = angleInDeg* (Math.PI/180.0) ;
+ invalidate();
+ }
+
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ final JSlider slider1 = new JSlider(SwingConstants.HORIZONTAL, 0, 3600, 0);
+ slider1.setValue((int) (rootAngle * 10));
+ slider1.setPaintTicks(true);
+ slider1.setPaintLabels(true);
+
+ slider1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = (slider1.getValue() / 10.0);
+ setRootAngle(value % 360);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Angle:", slider1, true);
+
+ controls = new Controls("Layout", optionsPanel, true);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+
+ protected void validate() {
+ nodePoints.clear();
+ branchPaths.clear();
+ taxonLabelPaths.clear();
+
+ try {
+ final Node root = tree.getRootNode();
+
+ constructNode(root, rootAngle, rootAngle + Math.PI * 2, 0.0, 0.0, 0.0);
+
+ if( !tree.conceptuallyUnrooted() ) {
+ Line2D branchPath = new Line2D.Double(0.0, 0.0, 0.0, 0.0);
+
+ // add the branchPath to the map of branch paths
+ branchPaths.put(root, branchPath);
+ }
+
+ } catch (Graph.NoEdgeException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+
+ private Point2D constructNode(final Node node,
+ final double angleStart, final double angleFinish,
+ final double xPosition, final double yPosition, final double length)
+ throws Graph.NoEdgeException {
+
+ final double branchAngle = (angleStart + angleFinish) / 2.0;
+
+ final double directionX = Math.cos(branchAngle);
+ final double directionY = Math.sin(branchAngle);
+
+ final double x = xPosition + (length * directionX);
+ final double y = yPosition + (length * directionY);
+ final Point2D nodePoint = new Point2D.Double(x, y);
+
+ // System.out.println("Node: " + Utils.DEBUGsubTreeRep(tree, node) + " at " + nodePoint);
+
+ final double x2 = xPosition + ((length + 1.0) * directionX);
+ final double y2 = yPosition + ((length + 1.0) * directionY);
+
+ if (!tree.isExternal(node)) {
+
+ List<Node> children = tree.getChildren(node);
+ int[] leafCounts = new int[children.size()];
+ int totalLeafs = 0;
+
+ for(int i = 0; i < children.size(); ++i) {
+ final Node child = children.get(i);
+ leafCounts[i] = jebl.evolution.trees.Utils.getExternalNodeCount(tree, child);
+ totalLeafs += leafCounts[i];
+ }
+
+ final double span = angleFinish - angleStart;
+ double a2 = angleStart;
+
+ for(int i = 0; i < children.size(); ++i) {
+ final Node child = children.get(i);
+
+ final double childLength = tree.getLength(child);
+ double a1 = a2;
+ a2 = a1 + (span * leafCounts[i] / totalLeafs);
+
+ final Point2D childPoint = constructNode(child, a1, a2, x, y, childLength);
+
+ double toY = childPoint.getY();
+ double toX = childPoint.getX();
+ if( childLength == 0 ) {
+ final double a = (a1 + a2) / 2.0;
+ double epsilon = 1e-9;
+ final double dx = Math.cos(a);
+ final double dy = Math.sin(a);
+ toX = x + epsilon * dx;
+ toY = y + epsilon * dy;
+ }
+
+ Line2D branchPath = new Line2D.Double(x, y, toX, toY);
+
+ // add the branchPath to the map of branch paths
+ branchPaths.put(child, branchPath);
+
+ branchLabelPaths.put(child, (Line2D)branchPath.clone());
+ }
+
+ final Point2D nodeLabelPoint = new Point2D.Double(x2, y2);
+
+ final Line2D nodeLabelPath = new Line2D.Double(nodePoint, nodeLabelPoint);
+ nodeLabelPaths.put(node, nodeLabelPath);
+ } else {
+
+ Point2D taxonPoint = new Point2D.Double(x2, y2);
+
+ final Line2D taxonLabelPath = new Line2D.Double(nodePoint, taxonPoint);
+ taxonLabelPaths.put(node, taxonLabelPath);
+ }
+
+ // add the node point to the map of node points
+ nodePoints.put(node, nodePoint);
+
+ return nodePoint;
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/RectilinearTreeLayout.java b/src/jebl/gui/trees/treeviewer/treelayouts/RectilinearTreeLayout.java
new file mode 100644
index 0000000..4e1c6be
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/RectilinearTreeLayout.java
@@ -0,0 +1,373 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Utils;
+import jebl.gui.trees.treeviewer.TreeViewer;
+import org.virion.jam.controlpanels.ControlPalette;
+import org.virion.jam.controlpanels.Controls;
+import org.virion.jam.controlpanels.ControlsSettings;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RectilinearTreeLayout.java 674 2007-03-28 05:23:26Z stevensh $
+ */
+public class RectilinearTreeLayout extends AbstractTreeLayout {
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.DISCRETE;
+ }
+
+ public boolean maintainAspectRatio() {
+ return false;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Line2D getHeightLine(double height) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public boolean alignTaxa() {
+ return alignTaxonLabels;
+ }
+
+ public Shape getCollapsedNode(Node node, double ratio) {
+ Node first = node;
+ while( ! tree.isExternal(first) ) {
+ first = tree.getChildren(first).get(0);
+ }
+ Node last = node;
+ while( ! tree.isExternal(last) ) {
+ final List<Node> children = tree.getChildren(last);
+ last = children.get(children.size()- 1);
+ }
+
+ final Point2D c1 = getNodePoint(first);
+ final Point2D cn = getNodePoint(last);
+ final Point2D n = getNodePoint(node);
+ final double dy = cn.getY() - c1.getY();
+ final double dx = cn.getX() - n.getX(); assert dx >= 0.0 && dy >= 0;
+
+ return new Rectangle2D.Double(n.getX(), n.getY() + (c1.getY() - n.getY()) * ratio, dx * ratio, dy * ratio);
+ }
+
+ public int getNodeMarkerRadiusUpperLimit(Node node, AffineTransform transform) {
+ //final Node parent = tree.getParent(node);
+ double lim = Double.MAX_VALUE;
+ final Point2D n = getNodePoint(node);
+ for( Node a : tree.getAdjacencies(node) ) {
+ //final Point2D n = getNodePoint(node);
+ final Point2D loc = getNodePoint(a);
+ final double d = Math.abs(n.getX() - loc.getX()) * transform.getScaleX();
+ lim = Math.min(lim, d);
+ }
+
+ final List<Node> nodes = tree.getChildren(node);
+ final int nNodes = nodes.size();
+ final double ratio = 0.10;
+ if( nNodes >= 2 ) {
+ final Point2D c1 = getNodePoint(nodes.get(0));
+ final Point2D cn = getNodePoint(nodes.get(nodes.size()-1));
+
+ final double d = ratio * (cn.getY() - c1.getY()) * transform.getScaleY();
+ lim = Math.min(lim, d);
+ } else if( nNodes == 0 ) {
+ final Node[] rnode = {Utils.rightNb(tree, node), Utils.leftNb(tree, node)};
+// System.err.println("right/left of " + tree.getTaxon(node).getName() + " "
+// + ((rnode[0] == null) ? " - " : tree.getTaxon(rnode[0]).getName()) + " " +
+// ((rnode[1] == null) ? " - " : tree.getTaxon(rnode[1]).getName()) );
+
+ for( Node nb : rnode ) {
+ if( nb != null ) {
+ final Point2D pt = getNodePoint(nb);
+ final double dy = ratio * Math.abs((pt.getY() - n.getY())) * transform.getScaleY();
+ double d;
+ if( pt.getX() > n.getX() ) {
+ d = dy;
+ } else {
+ final double dx = ratio * Math.abs((pt.getX() - n.getX())) * transform.getScaleX();
+ d = Math.sqrt(dx*dx + dy*dy);
+ }
+
+ lim = Math.min(lim, d);
+ }
+ }
+ }
+ assert lim >= 0;
+ return (int) lim;
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // do nothing...
+ }
+
+ public List<Controls> getControls(boolean detachPrimaryCheckbox) {
+ final Preferences prefs = Preferences.userNodeForPackage(TreeViewer.class);
+ List<Controls> controlsList = new ArrayList<Controls>();
+
+ if (controls == null) {
+ OptionsPanel optionsPanel = new OptionsPanel();
+
+ final int slider1max = 10000;
+ if( !tree.conceptuallyUnrooted() ) {
+ final JSlider slider1 = new JSlider(SwingConstants.HORIZONTAL, 0, slider1max, 0);
+ slider1.setValue((int) (rootLength * slider1max));
+
+ // don't make sense without setting spacing
+ //slider1.setPaintTicks(true);
+ //slider1.setPaintLabels(true);
+
+ slider1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = slider1.getValue();
+ setRootLength(value / slider1max);
+ prefs.putInt("root length",slider1.getValue());
+ }
+ });
+ slider1.setValue(prefs.getInt("root length",0));
+ optionsPanel.addComponentWithLabel("Root Length:", slider1, true);
+ }
+
+ final int slider2max = 100;
+ final JSlider slider2 = new JSlider(SwingConstants.HORIZONTAL, 0, slider2max, 0);
+ //slider2.setMajorTickSpacing(20);
+ //slider2.setPaintTicks(true);
+ //slider2.setPaintLabels(true);
+
+ slider2.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = 1.0 - (((double) slider2.getValue()) / slider2max);
+ setBranchCurveProportion(value, value);
+ prefs.putInt("tree curvature",slider2.getValue());
+ }
+ });
+ slider2.setValue(prefs.getInt("tree curvature",0));
+ optionsPanel.addComponentWithLabel("Curvature:", slider2, true);
+
+ final JCheckBox checkBox1 = new JCheckBox("Align Taxon Labels");
+
+ setAlignTaxonLabels(prefs.getBoolean("align taxon labels",alignTaxonLabels));
+ checkBox1.setSelected(alignTaxonLabels);
+ checkBox1.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ setAlignTaxonLabels(checkBox1.isSelected());
+ prefs.putBoolean("align taxon labels",checkBox1.isSelected());
+ }
+ });
+
+ optionsPanel.addComponent(checkBox1);
+
+ controls = new Controls("Layout", optionsPanel, true, false, null);
+ }
+
+ controlsList.add(controls);
+
+ return controlsList;
+ }
+
+ public void setSettings(ControlsSettings settings) {
+ }
+
+ public void getSettings(ControlsSettings settings) {
+ }
+
+ private Controls controls = null;
+
+ public void setRootLength(double rootLength) {
+ this.rootLength = rootLength;
+ invalidate();
+ }
+
+ public void setBranchCurveProportion(double xProportion, double yProportion) {
+ this.xProportion = xProportion;
+ this.yProportion = yProportion;
+ invalidate();
+ }
+
+ public void setAlignTaxonLabels(boolean alignTaxonLabels) {
+ this.alignTaxonLabels = alignTaxonLabels;
+ invalidate();
+ }
+
+ protected void validate() {
+ nodePoints.clear();
+ branchPaths.clear();
+ taxonLabelPaths.clear();
+ calloutPaths.clear();
+
+ maxXPosition = 0.0;
+
+ yPosition = 0.0;
+ yIncrement = 1.0 / (tree.getExternalNodes().size() + 1);
+
+ final Node root = this.tree.getRootNode();
+ final double rl = rootLength * this.tree.getHeight(root);
+
+ maxXPosition = 0.0;
+ getMaxXPosition(root, rl);
+
+ Point2D rootPoint = constructNode(root, rl);
+
+ // construct a root branch line
+ final Line2D line = new Line2D.Double(0.0, rootPoint.getY(), rootPoint.getX(), rootPoint.getY());
+
+ // add the line to the map of branch paths
+ branchPaths.put(root, line);
+ }
+
+ private Point2D constructNode(Node node, double xPosition) {
+
+ Point2D nodePoint;
+
+ if (!tree.isExternal(node)) {
+
+ double yPos = 0.0;
+
+ final List<Node> children = tree.getChildren(node);
+ for (Node child : children) {
+ final double length = tree.getLength(child);
+ final Point2D childPoint = constructNode(child, xPosition + length);
+ yPos += childPoint.getY();
+ }
+
+ // the y-position of the node is the average of the child nodes
+ yPos /= children.size();
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+ final double x = xPosition;
+ final double y = yPos;
+
+ for( final Node child : children ) {
+
+ final Point2D childPoint = nodePoints.get(child);
+
+ GeneralPath branchPath = new GeneralPath();
+
+ // start point
+ final float x0 = (float) x;
+ final float y0 = (float) y;
+
+ // end point
+ final double cx = childPoint.getX();
+ final float x1 = (float) cx;
+ final double yChild = childPoint.getY();
+ final float y1 = (float) yChild;
+
+ float x2 = x1 - ((x1 - x0) * (float) xProportion);
+ float y2 = y0 + ((y1 - y0) * (float) yProportion);
+
+ branchPath.moveTo(x0, y0);
+ branchPath.lineTo(x0, y2);
+ branchPath.quadTo(x0, y1, x2, y1);
+ branchPath.lineTo(x1, y1);
+
+ // add the branchPath to the map of branch paths
+ branchPaths.put(child, branchPath);
+
+ final boolean zeroBramch = cx == x;
+ final double dd = 0.0001;
+
+ Line2D branchLabelPath =
+ new Line2D.Double(zeroBramch ? x - dd :x, yChild, zeroBramch ? cx + dd : cx, yChild);
+
+ branchLabelPaths.put(child, branchLabelPath);
+ }
+
+ Line2D nodeLabelPath = new Line2D.Double(x, y, x + 1.0, y);
+
+ nodeLabelPaths.put(node, nodeLabelPath);
+
+ } else {
+
+ nodePoint = new Point2D.Double(xPosition, yPosition);
+ final double x = xPosition;
+ final double y = yPosition;
+
+ Line2D taxonLabelPath;
+
+ if (alignTaxonLabels) {
+
+ taxonLabelPath = new Line2D.Double(maxXPosition, y, maxXPosition + 1.0, y);
+
+ final Line2D calloutPath = new Line2D.Double(x, y, maxXPosition, y);
+
+ calloutPaths.put(node, calloutPath);
+
+ } else {
+ taxonLabelPath = new Line2D.Double(x, y, x + 1.0, y);
+ }
+
+ taxonLabelPaths.put(node, taxonLabelPath);
+
+ yPosition += yIncrement;
+
+ }
+
+ // add the node point to the map of node points
+ nodePoints.put(node, nodePoint);
+
+ return nodePoint;
+ }
+
+ public boolean smallSubTree(Node node, AffineTransform transform) {
+ int th = 7;
+ final List<Node> children = tree.getChildren(node);
+ if( children.size() < 2 ) return false;
+
+ final Node[] ch = {children.get(0), children.get(1)};
+// final Point2D[] loc = {new Point2D.Double(), new Point2D.Double()};
+// for(int k = 0; k < 2; ++k) {
+// final Point2D location = nodePoints.get(ch[k]);
+// transform.transform(location, loc[k]);
+// }
+ final double d = (nodePoints.get(ch[1]).getY() - nodePoints.get(ch[0]).getY()) * transform.getScaleY();
+ assert d >= 0;
+ //final double d = Math.abs(loc[0].getY() - loc[1].getY())
+ return d <= th;
+ }
+
+ private void getMaxXPosition(final Node node, final double xPosition) {
+
+ if ( tree.isExternal(node)) {
+ if (xPosition > maxXPosition) {
+ maxXPosition = xPosition;
+ }
+ } else {
+ for (final Node child : tree.getChildren(node)) {
+ getMaxXPosition(child, xPosition + tree.getLength(child));
+ }
+ }
+ }
+
+ private double yPosition;
+ private double yIncrement;
+
+ private double maxXPosition;
+
+ private double xProportion = 1.0;
+ private double yProportion = 1.0;
+
+ private double rootLength = 0.01;
+
+ private boolean alignTaxonLabels = false;
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayout.java b/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayout.java
new file mode 100644
index 0000000..6b27073
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayout.java
@@ -0,0 +1,135 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Tree;
+import org.virion.jam.controlpanels.ControlsProvider;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeLayout.java 603 2007-01-02 21:20:55Z pepster $
+ */
+public interface TreeLayout extends ControlsProvider {
+
+ enum AxisType {
+ CONTINUOUS,
+ DISCRETE
+ }
+
+ /**
+ * Set the tree for the layout0
+ *
+ * @param tree
+ */
+ void setTree(Tree tree);
+
+ /**
+ * Add a listener for this layout
+ *
+ * @param listener
+ */
+ void addTreeLayoutListener(TreeLayoutListener listener);
+
+ /**
+ * Remove a listener from this layout
+ *
+ * @param listener
+ */
+ void removeTreeLayoutListener(TreeLayoutListener listener);
+
+ /**
+ * Force the layout to re-layout all its components
+ */
+ void invalidate();
+
+ /**
+ * Return whether the x axis is continuous or discrete
+ *
+ * @return the axis type
+ */
+ AxisType getXAxisType();
+
+ /**
+ * Return whether the y axis is continuous or discrete
+ *
+ * @return the axis type
+ */
+ AxisType getYAxisType();
+
+ /**
+ * Return whether the two axis scales should be maintained
+ * relative to each other. Implies centering of the tree as well.
+ *
+ * @return aspect indication
+ */
+ boolean maintainAspectRatio();
+
+ /**
+ * Return the height (from the youngest tip) for the given
+ * 2d point. Some layouts won't be able to produce this and
+ * may throw an UnsupportedOperationException.
+ *
+ * @param point
+ * @return the height
+ */
+ double getHeightOfPoint(Point2D point);
+
+ /**
+ * Return a line that defines a particular height. Some layouts
+ * won't be able to produce this and may throw an UnsupportedOperationException.
+ *
+ * @param height
+ * @return the line
+ */
+ Line2D getHeightLine(double height);
+
+ /**
+ * Return a shape that defines a particular height interval. Some layouts
+ * won't be able to produce this and may throw an UnsupportedOperationException.
+ *
+ * @param height1
+ * @param height2
+ * @return the area
+ */
+ Shape getHeightArea(double height1, double height2);
+
+ /**
+ * Return the point in 2d space of the given node
+ *
+ * @param node
+ * @return the point
+ */
+ Point2D getNodePoint(Node node);
+
+ /**
+ *
+ * @return true if all taxa should have the same width, false otherwise
+ */
+ boolean alignTaxa();
+
+ /**
+ * Return the shape that represents the branch from node parent to itself.
+ *
+ * @param node
+ * @return the branch as a shape
+ */
+ Shape getBranchPath(Node node);
+
+ Line2D getTaxonLabelPath(Node node);
+
+ Line2D getBranchLabelPath(Node node);
+
+ Line2D getNodeLabelPath(Node node);
+
+ Shape getCalloutPath(Node node);
+
+ Shape getCollapsedNode(Node node, double ratio);
+
+ int getNodeMarkerRadiusUpperLimit(Node node, AffineTransform transform);
+
+ boolean smallSubTree(Node node, AffineTransform transform);
+}
diff --git a/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayoutListener.java b/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayoutListener.java
new file mode 100644
index 0000000..c915fda
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer/treelayouts/TreeLayoutListener.java
@@ -0,0 +1,11 @@
+package jebl.gui.trees.treeviewer.treelayouts;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeLayoutListener.java 181 2006-01-23 17:31:10Z rambaut $
+ */
+public interface TreeLayoutListener {
+
+ void treeLayoutChanged();
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/DefaultTreeViewer.java b/src/jebl/gui/trees/treeviewer_dev/DefaultTreeViewer.java
new file mode 100644
index 0000000..ed9f37c
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/DefaultTreeViewer.java
@@ -0,0 +1,472 @@
+/*
+ * AlignmentPanel.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.*;
+import jebl.gui.trees.treeviewer_dev.treelayouts.TreeLayout;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+import jebl.gui.trees.treeviewer_dev.painters.LabelPainter;
+import jebl.gui.trees.treeviewer_dev.painters.*;
+import jebl.gui.trees.treeviewer_dev.painters.ScaleBarPainter;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.print.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: DefaultTreeViewer.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class DefaultTreeViewer extends TreeViewer {
+
+ private final static double MAX_ZOOM = 20;
+ private final static double MAX_VERTICAL_EXPANSION = 20;
+
+ /**
+ * Creates new TreeViewer
+ */
+ public DefaultTreeViewer() {
+ setLayout(new BorderLayout());
+
+ this.treePane = new TreePane();
+ treePane.setAutoscrolls(true); //enable synthetic drag events
+
+ JScrollPane scrollPane = new JScrollPane(treePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ scrollPane.setMinimumSize(new Dimension(150, 150));
+
+ scrollPane.setBorder(null);
+ viewport = scrollPane.getViewport();
+
+ add(scrollPane, BorderLayout.CENTER);
+
+ // This overrides MouseListener and MouseMotionListener to allow selection in the TreePane -
+ // It installs itself within the constructor.
+ treePaneSelector = new TreePaneSelector(treePane);
+ }
+
+ public void setTree(Tree tree) {
+ trees.clear();
+ addTree(tree);
+ showTree(0);
+ }
+
+ public void setTrees(Collection<? extends Tree> trees) {
+ this.trees.clear();
+ for (Tree tree : trees) {
+ addTree(tree);
+ }
+ showTree(0);
+ }
+
+ public void addTree(Tree tree) {
+ this.trees.add(tree);
+
+ if (treePane.getTipLabelPainter() != null) {
+ treePane.getTipLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getBranchLabelPainter() != null) {
+ treePane.getBranchLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getNodeLabelPainter() != null) {
+ treePane.getNodeLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getNodeBarPainter() != null) {
+ treePane.getNodeBarPainter().setupAttributes(trees);
+ }
+ }
+
+ public void addTrees(Collection<? extends Tree> trees) {
+ int count = getTreeCount();
+ for (Tree tree : trees) {
+ addTree(tree);
+ }
+ showTree(count);
+ }
+
+ public List<Tree> getTrees() {
+ return trees;
+ }
+
+ public Tree getCurrentTree() {
+ return trees.get(currentTreeIndex);
+ }
+
+ public int getCurrentTreeIndex() {
+ return currentTreeIndex;
+ }
+
+ public int getTreeCount() {
+ if (trees == null) return 0;
+ return trees.size();
+ }
+
+ public void showTree(int index) {
+ Tree tree = trees.get(index);
+ if (tree instanceof RootedTree) {
+ treePane.setTree((RootedTree)tree);
+ } else {
+ treePane.setTree(Utils.rootTheTree(tree));
+ }
+
+ currentTreeIndex = index;
+ fireTreeChanged();
+ }
+
+ public void showNextTree() {
+ if (currentTreeIndex < trees.size() - 1) {
+ showTree(currentTreeIndex + 1);
+ }
+ }
+
+ public void showPreviousTree() {
+ if (currentTreeIndex > 0) {
+ showTree(currentTreeIndex - 1);
+ }
+ }
+
+ public void setTreeLayout(TreeLayout treeLayout) {
+ treePane.setTreeLayout(treeLayout);
+ fireTreeSettingsChanged();
+ }
+
+ private boolean zoomPending = false;
+ private double zoom = 0.0, verticalExpansion = 0.0;
+
+ public void setZoom(double zoom) {
+ this.zoom = zoom * MAX_ZOOM;
+ refreshZoom();
+ }
+
+ public void setVerticalExpansion(double verticalExpansion) {
+ this.verticalExpansion = verticalExpansion * MAX_VERTICAL_EXPANSION;
+ refreshZoom();
+ }
+
+ public boolean verticalExpansionAllowed() {
+ return !treePane.maintainAspectRatio();
+ }
+
+ private void refreshZoom() {
+ setZoom(zoom, zoom + verticalExpansion);
+ }
+
+ private void setZoom(double xZoom, double yZoom) {
+
+ Dimension viewportSize = viewport.getViewSize();
+ Point position = viewport.getViewPosition();
+
+ Dimension extentSize = viewport.getExtentSize();
+ double w = extentSize.getWidth() * (1.0 + xZoom);
+ double h = extentSize.getHeight() * (1.0 + yZoom);
+
+ Dimension newSize = new Dimension((int) w, (int) h);
+ treePane.setPreferredSize(newSize);
+
+ double cx = position.getX() + (0.5 * extentSize.getWidth());
+ double cy = position.getY() + (0.5 * extentSize.getHeight());
+
+ double rx = ((double) newSize.getWidth()) / viewportSize.getWidth();
+ double ry = ((double) newSize.getHeight()) / viewportSize.getHeight();
+
+ double px = (cx * rx) - (extentSize.getWidth() / 2.0);
+ double py = (cy * ry) - (extentSize.getHeight() / 2.0);
+
+ Point newPosition = new Point((int) px, (int) py);
+ viewport.setViewPosition(newPosition);
+ treePane.revalidate();
+ }
+
+ public boolean hasSelection() {
+ return treePane.hasSelection();
+ }
+
+ public void selectTaxa(SearchType searchType, String searchString, boolean caseSensitive) {
+ treePane.clearSelection();
+
+ if (searchType == SearchType.MATCHES && !caseSensitive) {
+ throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+ }
+
+ String query = (caseSensitive ? searchString : searchString.toUpperCase());
+
+ Tree tree = treePane.getTree();
+
+ for (Node node : tree.getExternalNodes()) {
+ Taxon taxon = tree.getTaxon(node);
+ String target = (caseSensitive ? taxon.getName() : taxon.getName().toUpperCase());
+ switch (searchType) {
+ case CONTAINS:
+ if (target.contains(query)) {
+ treePane.addSelectedTip(node);
+ }
+ break;
+ case STARTS_WITH:
+ if (target.startsWith(query)) {
+ treePane.addSelectedTip(node);
+ }
+ break;
+ case ENDS_WITH:
+ if (target.endsWith(query)) {
+ treePane.addSelectedTip(node);
+ }
+ break;
+ case MATCHES:
+ if (target.matches(query)) {
+ treePane.addSelectedTip(node);
+ }
+ break;
+ }
+ }
+ }
+
+ public void selectNodes(String attribute, SearchType searchType, String searchString, boolean caseSensitive) {
+ treePane.clearSelection();
+
+ if (searchType == SearchType.MATCHES && !caseSensitive) {
+ throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+ }
+
+ String query = (caseSensitive ? searchString : searchString.toUpperCase());
+
+ Tree tree = treePane.getTree();
+
+ for (Node node : tree.getNodes()) {
+ Object value = node.getAttribute(attribute);
+
+ if (value != null) {
+ String target = (caseSensitive ?
+ value.toString() : value.toString().toUpperCase());
+ switch (searchType) {
+ case CONTAINS:
+ if (target.contains(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case STARTS_WITH:
+ if (target.startsWith(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case ENDS_WITH:
+ if (target.endsWith(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ case MATCHES:
+ if (target.matches(query)) {
+ treePane.addSelectedNode(node);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ public void cartoonSelectedNodes() {
+ treePane.cartoonSelectedNodes();
+ fireTreeSettingsChanged();
+ }
+
+ public void collapseSelectedNodes() {
+ treePane.collapseSelectedNodes();
+ fireTreeSettingsChanged();
+ }
+
+ public void annotateSelectedNodes(String name, Object value) {
+ treePane.annotateSelectedNodes(name, value);
+ fireTreeSettingsChanged();
+ }
+
+ public void annotateSelectedTips(String name, Object value) {
+ treePane.annotateSelectedTips(name, value);
+ fireTreeSettingsChanged();
+ }
+
+ public void selectAll() {
+ if (treePaneSelector.getSelectionMode() == TreePaneSelector.SelectionMode.TAXA) {
+ treePane.selectAllTaxa();
+ } else {
+ treePane.selectAllNodes();
+ }
+ }
+
+ public void clearSelectedTaxa() {
+ treePane.clearSelection();
+ }
+
+ public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treePane.addTreeSelectionListener(treeSelectionListener);
+ }
+
+ public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treePane.removeTreeSelectionListener(treeSelectionListener);
+ }
+
+ public void setSelectionMode(TreePaneSelector.SelectionMode selectionMode) {
+ TreePaneSelector.SelectionMode oldSelectionMode = treePaneSelector.getSelectionMode();
+
+ if (selectionMode == oldSelectionMode) {
+ return;
+ }
+
+ if (oldSelectionMode == TreePaneSelector.SelectionMode.TAXA) {
+ treePane.selectNodesFromSelectedTips();
+ } else if (selectionMode == TreePaneSelector.SelectionMode.TAXA) {
+ treePane.selectTipsFromSelectedNodes();
+ } else if (selectionMode == TreePaneSelector.SelectionMode.CLADE) {
+ treePane.selectCladesFromSelectedNodes();
+ }
+ treePaneSelector.setSelectionMode(selectionMode);
+ }
+
+ public void setDragMode(TreePaneSelector.DragMode dragMode) {
+ treePaneSelector.setDragMode(dragMode);
+ }
+
+ // A load of deligated method calls through to treePane (which is now hidden outside the package).
+ public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) {
+ treePane.setTipLabelPainter(tipLabelPainter);
+ tipLabelPainter.setupAttributes(trees);
+
+ }
+
+ public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) {
+ treePane.setNodeLabelPainter(nodeLabelPainter);
+ nodeLabelPainter.setupAttributes(trees);
+ }
+
+ public void setNodeBarPainter(NodeBarPainter nodeBarPainter) {
+ treePane.setNodeBarPainter(nodeBarPainter);
+ nodeBarPainter.setupAttributes(trees);
+ }
+
+ //TEST
+ public void setNodeHistPainter(NodeHistPainter nodeHistPainter) {
+ treePane.setNodeHistPainter(nodeHistPainter);
+ nodeHistPainter.setupAttributes(trees);
+ }
+
+ //TEST END
+
+ public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) {
+ treePane.setBranchLabelPainter(branchLabelPainter);
+ branchLabelPainter.setupAttributes(trees);
+ }
+
+ public void setScaleBarPainter(ScaleBarPainter scaleBarPainter) {
+ treePane.setScaleBarPainter(scaleBarPainter);
+ }
+
+ public void setBranchDecorator(Decorator branchDecorator) {
+ treePane.setBranchDecorator(branchDecorator);
+ }
+
+ public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) {
+ treePane.setBranchColouringDecorator(branchColouringAttribute, branchColouringDecorator);
+ }
+
+ public void setSelectionPaint(Paint selectionPane) {
+ treePane.setSelectionPaint(selectionPane);
+ }
+
+ public Paint getSelectionPaint() {
+ return treePane.getSelectionPaint();
+ }
+
+ public void setBranchStroke(BasicStroke branchStroke) {
+ treePane.setBranchStroke(branchStroke);
+ }
+
+ public boolean isTransformBranchesOn() {
+ return treePane.isTransformBranchesOn();
+ }
+
+ public TransformedRootedTree.Transform getBranchTransform() {
+ return treePane.getBranchTransform();
+ }
+
+ public void setTransformBranchesOn(boolean transformBranchesOn) {
+ treePane.setTransformBranchesOn(transformBranchesOn);
+ }
+
+ public void setBranchTransform(TransformedRootedTree.Transform transform) {
+ treePane.setBranchTransform(transform);
+ }
+
+ public boolean isOrderBranchesOn() {
+ return treePane.isOrderBranchesOn();
+ }
+
+ public SortedRootedTree.BranchOrdering getBranchOrdering() {
+ return treePane.getBranchOrdering();
+ }
+
+ public void setOrderBranchesOn(boolean orderBranchesOn) {
+ treePane.setOrderBranchesOn(orderBranchesOn);
+ }
+
+ public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) {
+ treePane.setBranchOrdering(branchOrdering);
+ }
+
+ public JComponent getContentPane() {
+ return treePane;
+ }
+
+ public void paint(Graphics g) {
+ if( zoomPending ) {
+ refreshZoom();
+ zoomPending = false;
+ }
+ super.paint(g);
+ }
+
+ public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
+ return treePane.print(g, pageFormat, pageIndex);
+ }
+
+ public void addTreeViewerListener(TreeViewerListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTreeViewerListener(TreeViewerListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void fireTreeChanged() {
+ for (TreeViewerListener listener : listeners) {
+ listener.treeChanged();
+ }
+ }
+
+ public void fireTreeSettingsChanged() {
+ for (TreeViewerListener listener : listeners) {
+ listener.treeSettingsChanged();
+ }
+ }
+
+ private java.util.List<TreeViewerListener> listeners = new ArrayList<TreeViewerListener>();
+
+ private List<Tree> trees = new ArrayList<Tree>();
+ private int currentTreeIndex = 0;
+
+ protected TreePane treePane;
+ protected TreePaneSelector treePaneSelector;
+
+ protected JViewport viewport;
+
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewer.java b/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewer.java
new file mode 100644
index 0000000..2f3c81d
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewer.java
@@ -0,0 +1,561 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.trees.*;
+import jebl.evolution.graphs.Node;
+import jebl.gui.trees.treeviewer_dev.treelayouts.TreeLayout;
+import jebl.gui.trees.treeviewer_dev.painters.*;
+import jebl.gui.trees.treeviewer_dev.painters.NodeBarPainter;
+import jebl.gui.trees.treeviewer_dev.painters.ScaleBarPainter;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.print.*;
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: MultiPaneTreeViewer.java 537 2006-11-22 22:24:54Z rambaut $
+ */
+public class MultiPaneTreeViewer extends TreeViewer {
+
+ private final static double MAX_ZOOM = 20;
+ private final static double MAX_VERTICAL_EXPANSION = 20;
+
+ /**
+ * Creates new TreeViewer
+ */
+ public MultiPaneTreeViewer() {
+ treePanes.add(new TreePane());
+
+ setLayout(new BorderLayout());
+
+ treePanePanel = new MultiPaneTreePanel();
+ treePanePanel.setLayout(new BoxLayout(treePanePanel, BoxLayout.PAGE_AXIS));
+
+ JScrollPane scrollPane = new JScrollPane(treePanePanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ scrollPane.setMinimumSize(new Dimension(150, 150));
+
+ scrollPane.setBorder(null);
+ viewport = scrollPane.getViewport();
+
+ add(scrollPane, BorderLayout.CENTER);
+
+ }
+
+ public void setTree(Tree tree) {
+ trees.clear();
+ addTree(tree);
+ showTree(0);
+ }
+
+ public void setTrees(Collection<? extends Tree> trees) {
+ this.trees.clear();
+ for (Tree tree : trees) {
+ addTree(tree);
+ }
+ showTree(0);
+ }
+
+ protected void addTree(Tree tree) {
+ this.trees.add(tree);
+ showTree(trees.size() - 1);
+
+ TreePane treePane = treePanes.get(0);
+ if (treePane.getTipLabelPainter() != null) {
+ treePane.getTipLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getBranchLabelPainter() != null) {
+ treePane.getBranchLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getNodeLabelPainter() != null) {
+ treePane.getNodeLabelPainter().setupAttributes(trees);
+ }
+
+ if (treePane.getNodeBarPainter() != null) {
+ treePane.getNodeBarPainter().setupAttributes(trees);
+ }
+ }
+
+ public void addTrees(Collection<? extends Tree> trees) {
+ int count = getTreeCount();
+ for (Tree tree : trees) {
+ addTree(tree);
+ }
+ showTree(count);
+ }
+
+ public Tree getTree() {
+ return trees.get(0);
+ }
+
+ public java.util.List<Tree> getTrees() {
+ return trees;
+ }
+
+ public int getTreesPerPage() {
+ return treesPerPage;
+ }
+
+ public void setTreesPerPage(int treesPerPage) {
+ this.treesPerPage = treesPerPage;
+ if (treePanes.size() < treesPerPage) {
+ while (treePanes.size() < treesPerPage) {
+ treePanes.add(new TreePane());
+ }
+ } else if (treePanes.size() > treesPerPage) {
+ while (treePanes.size() > treesPerPage) {
+ treePanes.remove(treePanes.size() - 1);
+ }
+ }
+ showTree(currentTreeIndex);
+ }
+
+ private void setupTreePane(TreePane treePane) {
+ treePane.setAutoscrolls(true); //enable synthetic drag events
+
+ // This overrides MouseListener and MouseMotionListener to allow selection in the TreePane -
+ // It installs itself within the constructor.
+ treePaneSelector = new TreePaneSelector(treePane);
+ }
+
+ public Tree getCurrentTree() {
+ return trees.get(currentTreeIndex);
+ }
+
+
+ public int getCurrentTreeIndex() {
+ return currentTreeIndex;
+ }
+
+ public int getTreeCount() {
+ if (trees == null) return 0;
+ return trees.size();
+ }
+
+ public void showTree(int index) {
+ int i = index;
+ for (TreePane treePane : treePanes) {
+ if (i < trees.size()) {
+ Tree tree = trees.get(i);
+
+ if (tree instanceof RootedTree) {
+ treePane.setTree((RootedTree)tree);
+ } else {
+ treePane.setTree(Utils.rootTheTree(tree));
+ }
+ } else {
+ treePane.setTree(null);
+ }
+ i++;
+ }
+ currentTreeIndex = index;
+
+ treePanePanel.removeAll();
+ setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+ for (TreePane treePane : treePanes) {
+ treePanePanel.add(treePane);
+ setupTreePane(treePane);
+ }
+
+ fireTreeChanged();
+ }
+
+ public void showNextTree() {
+ if (currentTreeIndex < trees.size() - 1) {
+ showTree(currentTreeIndex + 1);
+ }
+ }
+
+ public void showPreviousTree() {
+ if (currentTreeIndex > 0) {
+ showTree(currentTreeIndex - 1);
+ }
+ }
+
+ public void setTreeLayout(TreeLayout treeLayout) {
+ for (TreePane treePane : treePanes) {
+ treePane.setTreeLayout(treeLayout);
+ }
+ }
+
+ private boolean zoomPending = false;
+ private double zoom = 0.0, verticalExpansion = 0.0;
+
+ public void setZoom(double zoom) {
+ this.zoom = zoom * MAX_ZOOM;
+ refreshZoom();
+ }
+
+ public void setVerticalExpansion(double verticalExpansion) {
+ this.verticalExpansion = verticalExpansion * MAX_VERTICAL_EXPANSION;
+ refreshZoom();
+ }
+
+ public boolean verticalExpansionAllowed() {
+ return !treePanes.get(0).maintainAspectRatio();
+ }
+
+ private void refreshZoom() {
+ setZoom(zoom, zoom + verticalExpansion);
+ }
+
+ private void setZoom(double xZoom, double yZoom) {
+
+ Dimension viewportSize = viewport.getViewSize();
+ Point position = viewport.getViewPosition();
+
+ Dimension extentSize = viewport.getExtentSize();
+ double w = extentSize.getWidth() * (1.0 + xZoom);
+ double h = extentSize.getHeight() * (1.0 + yZoom);
+
+ Dimension newSize = new Dimension((int) w, (int) h / treesPerPage);
+ for (TreePane treePane : treePanes) {
+ treePane.setPreferredSize(newSize);
+ treePane.revalidate();
+ }
+
+ double cx = position.getX() + (0.5 * extentSize.getWidth());
+ double cy = position.getY() + (0.5 * extentSize.getHeight());
+
+ double rx = ((double) newSize.getWidth()) / viewportSize.getWidth();
+ double ry = ((double) newSize.getHeight()) / viewportSize.getHeight();
+
+ double px = (cx * rx) - (extentSize.getWidth() / 2.0);
+ double py = (cy * ry) - (extentSize.getHeight() / 2.0);
+
+ Point newPosition = new Point((int) px, (int) py);
+ viewport.setViewPosition(newPosition);
+ }
+
+ public boolean hasSelection() {
+ for (TreePane treePane : treePanes) {
+ if (treePane.hasSelection()) return true;
+ }
+ return false;
+ }
+
+ public void selectTaxa(MultiPaneTreeViewer.SearchType searchType, String searchString, boolean caseSensitive) {
+// treePane.clearSelection();
+//
+// if (searchType == MultiPaneTreeViewer.SearchType.MATCHES && !caseSensitive) {
+// throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+// }
+//
+// String query = (caseSensitive ? searchString : searchString.toUpperCase());
+//
+// Tree tree = treePane.getTree();
+//
+// for (Node node : tree.getExternalNodes()) {
+// Taxon taxon = tree.getTaxon(node);
+// String target = (caseSensitive ? taxon.getName() : taxon.getName().toUpperCase());
+// switch (searchType) {
+// case CONTAINS:
+// if (target.contains(query)) {
+// treePane.addSelectedTip(node);
+// }
+// break;
+// case STARTS_WITH:
+// if (target.startsWith(query)) {
+// treePane.addSelectedTip(node);
+// }
+// break;
+// case ENDS_WITH:
+// if (target.endsWith(query)) {
+// treePane.addSelectedTip(node);
+// }
+// break;
+// case MATCHES:
+// if (target.matches(query)) {
+// treePane.addSelectedTip(node);
+// }
+// break;
+// }
+// }
+ }
+
+ public void selectNodes(String attribute, MultiPaneTreeViewer.SearchType searchType, String searchString, boolean caseSensitive) {
+// treePane.clearSelection();
+//
+// if (searchType == MultiPaneTreeViewer.SearchType.MATCHES && !caseSensitive) {
+// throw new IllegalArgumentException("Regular expression matching cannot be case-insensitive");
+// }
+//
+// String query = (caseSensitive ? searchString : searchString.toUpperCase());
+//
+// Tree tree = treePane.getTree();
+//
+// for (Node node : tree.getNodes()) {
+// Object value = node.getAttribute(attribute);
+//
+// if (value != null) {
+// String target = (caseSensitive ?
+// value.toString() : value.toString().toUpperCase());
+// switch (searchType) {
+// case CONTAINS:
+// if (target.contains(query)) {
+// treePane.addSelectedNode(node);
+// }
+// break;
+// case STARTS_WITH:
+// if (target.startsWith(query)) {
+// treePane.addSelectedNode(node);
+// }
+// break;
+// case ENDS_WITH:
+// if (target.endsWith(query)) {
+// treePane.addSelectedNode(node);
+// }
+// break;
+// case MATCHES:
+// if (target.matches(query)) {
+// treePane.addSelectedNode(node);
+// }
+// break;
+// }
+// }
+// }
+ }
+
+ public void collapseSelectedNodes() {
+// treePane.collapseSelectedNodes();
+ }
+
+ public void annotateSelectedNodes(String name, Object value) {
+// treePane.annotateSelectedNodes(name, value);
+ fireTreeSettingsChanged();
+ }
+
+ public void annotateSelectedTips(String name, Object value) {
+ // treePane.annotateSelectedTips(name, value);
+ fireTreeSettingsChanged();
+ }
+
+ public void selectAll() {
+// if (treePaneSelector.getSelectionMode() == TreePaneSelector.SelectionMode.TAXA) {
+// treePane.selectAllTaxa();
+// } else {
+// treePane.selectAllNodes();
+// }
+ }
+
+ public void clearSelectedTaxa() {
+// treePane.clearSelection();
+ }
+
+ public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ for (TreePane treePane : treePanes) {
+ treePane.addTreeSelectionListener(treeSelectionListener);
+ }
+ }
+
+ public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ for (TreePane treePane : treePanes) {
+ treePane.removeTreeSelectionListener(treeSelectionListener);
+ }
+ }
+
+ public void setSelectionMode(TreePaneSelector.SelectionMode selectionMode) {
+// TreePaneSelector.SelectionMode oldSelectionMode = treePaneSelector.getSelectionMode();
+//
+// if (selectionMode == oldSelectionMode) {
+// return;
+// }
+//
+// if (oldSelectionMode == TreePaneSelector.SelectionMode.TAXA) {
+// treePane.selectNodesFromSelectedTips();
+// } else if (selectionMode == TreePaneSelector.SelectionMode.TAXA) {
+// treePane.selectTipsFromSelectedNodes();
+// } else if (selectionMode == TreePaneSelector.SelectionMode.CLADE) {
+// treePane.selectCladesFromSelectedNodes();
+// }
+// treePaneSelector.setSelectionMode(selectionMode);
+ }
+
+ public void setDragMode(TreePaneSelector.DragMode dragMode) {
+ treePaneSelector.setDragMode(dragMode);
+ }
+
+ // A load of deligated method calls through to treePane (which is now hidden outside the package).
+ public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setTipLabelPainter(tipLabelPainter);
+ }
+ tipLabelPainter.setupAttributes(trees);
+ fireTreeSettingsChanged();
+ }
+
+ public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setNodeLabelPainter(nodeLabelPainter);
+ }
+ nodeLabelPainter.setupAttributes(trees);
+ fireTreeSettingsChanged();
+ }
+
+ public void setNodeBarPainter(NodeBarPainter nodeBarPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setNodeBarPainter(nodeBarPainter);
+ }
+ nodeBarPainter.setupAttributes(trees);
+ fireTreeSettingsChanged();
+ }
+
+ //TEST
+ public void setNodeHistPainter(NodeHistPainter nodeHistPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setNodeHistPainter(nodeHistPainter);
+ }
+ nodeHistPainter.setupAttributes(trees);
+ fireTreeSettingsChanged();
+ }
+ //END TEST
+ public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchLabelPainter(branchLabelPainter);
+ }
+ branchLabelPainter.setupAttributes(trees);
+ fireTreeSettingsChanged();
+ }
+
+ public void setScaleBarPainter(ScaleBarPainter scaleBarPainter) {
+ for (TreePane treePane : treePanes) {
+ treePane.setScaleBarPainter(scaleBarPainter);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public void setBranchDecorator(Decorator branchDecorator) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchDecorator(branchDecorator);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchColouringDecorator(branchColouringAttribute, branchColouringDecorator);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public void setSelectionPaint(Paint selectionPane) {
+ for (TreePane treePane : treePanes) {
+ treePane.setSelectionPaint(selectionPane);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public Paint getSelectionPaint() {
+ return treePanes.get(0).getSelectionPaint();
+ }
+
+ public void setBranchStroke(BasicStroke branchStroke) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchStroke(branchStroke);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public boolean isTransformBranchesOn() {
+ return treePanes.get(0).isTransformBranchesOn();
+ }
+
+ public TransformedRootedTree.Transform getBranchTransform() {
+ return treePanes.get(0).getBranchTransform();
+ }
+
+ public void setTransformBranchesOn(boolean transformBranchesOn) {
+ for (TreePane treePane : treePanes) {
+ treePane.setTransformBranchesOn(transformBranchesOn);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public void setBranchTransform(TransformedRootedTree.Transform transform) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchTransform(transform);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public boolean isOrderBranchesOn() {
+ return treePanes.get(0).isOrderBranchesOn();
+ }
+
+ public SortedRootedTree.BranchOrdering getBranchOrdering() {
+ return treePanes.get(0).getBranchOrdering();
+ }
+
+ public void setOrderBranchesOn(boolean orderBranchesOn) {
+ for (TreePane treePane : treePanes) {
+ treePane.setOrderBranchesOn(orderBranchesOn);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) {
+ for (TreePane treePane : treePanes) {
+ treePane.setBranchOrdering(branchOrdering);
+ }
+ fireTreeSettingsChanged();
+ }
+
+ public JComponent getContentPane() {
+ return treePanePanel;
+ }
+
+ public void paint(Graphics g) {
+ if( zoomPending ) {
+ refreshZoom();
+ zoomPending = false;
+ }
+ super.paint(g);
+ }
+
+ public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
+ return treePanePanel.print(g, pageFormat, pageIndex);
+ }
+
+ public void addTreeViewerListener(TreeViewerListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTreeViewerListener(TreeViewerListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void fireTreeChanged() {
+ for (TreeViewerListener listener : listeners) {
+ listener.treeChanged();
+ }
+ }
+
+ public void fireTreeSettingsChanged() {
+ for (TreeViewerListener listener : listeners) {
+ listener.treeSettingsChanged();
+ }
+ }
+
+ private java.util.List<TreeViewerListener> listeners = new ArrayList<TreeViewerListener>();
+
+ private java.util.List<Tree> trees = new ArrayList<Tree>();
+ private java.util.List<TreePane> treePanes = new ArrayList<TreePane>();
+ private int currentTreeIndex = 0;
+ private int treesPerPage = 1;
+
+ private MultiPaneTreePanel treePanePanel;
+ protected TreePaneSelector treePaneSelector;
+ protected JViewport viewport;
+
+ class MultiPaneTreePanel extends JPanel implements Printable {
+
+ public int print(Graphics graphics, PageFormat pageFormat, int i) throws PrinterException {
+ return 0;
+ }
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewerController.java b/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewerController.java
new file mode 100644
index 0000000..4a614d5
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/MultiPaneTreeViewerController.java
@@ -0,0 +1,94 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.trees.Tree;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: MultiPaneTreeViewerController.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class MultiPaneTreeViewerController extends AbstractController {
+
+ public MultiPaneTreeViewerController(final MultiPaneTreeViewer treeViewer) {
+
+ titleLabel = new JLabel("Current Tree");
+ optionsPanel = new OptionsPanel();
+
+ final JLabel treeNameLabel = new JLabel("Tree 1");
+ final SpinnerNumberModel spinnerModel = new SpinnerNumberModel(1, 1, 1, 1);
+ JSpinner currentTreeSpinner = new JSpinner(spinnerModel);
+
+ currentTreeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ treeViewer.showTree((Integer)spinnerModel.getValue() - 1);
+ }
+ });
+
+ final JComboBox treesPerPageCombo = new JComboBox(new String[] { "1", "2", "3", "4", "5", "6", "7", "8" });
+ treesPerPageCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ treeViewer.setTreesPerPage(treesPerPageCombo.getSelectedIndex() + 1);
+ }
+ });
+
+
+ treeViewer.addTreeViewerListener(new TreeViewerListener() {
+ public void treeChanged() {
+ int index = treeViewer.getCurrentTreeIndex() + 1;
+ int treeCount = treeViewer.getTrees().size();
+ Tree tree = treeViewer.getCurrentTree();
+ spinnerModel.setValue(index);
+ spinnerModel.setMaximum(treeCount);
+ String name = (String)tree.getAttribute("name");
+ if (name != null) {
+ treeNameLabel.setText(name);
+ } else {
+ treeNameLabel.setText("Tree " + index);
+ }
+ titleLabel.setText("Current Tree: " + index + " / " + treeCount);
+ }
+
+ public void treeSettingsChanged() {
+ // nothing to do
+ }
+ });
+ optionsPanel.addComponentWithLabel("Name:", treeNameLabel);
+ optionsPanel.addComponentWithLabel("Tree:", currentTreeSpinner);
+ optionsPanel.addComponentWithLabel("Trees per page:", treesPerPageCombo);
+
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return true;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ }
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/MultipleTreesController.java b/src/jebl/gui/trees/treeviewer_dev/MultipleTreesController.java
new file mode 100644
index 0000000..583ee42
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/MultipleTreesController.java
@@ -0,0 +1,88 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.trees.Tree;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: MultipleTreesController.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class MultipleTreesController extends AbstractController {
+
+ public MultipleTreesController(final TreeViewer treeViewer) {
+
+ titleLabel = new JLabel("Current Tree");
+ optionsPanel = new OptionsPanel();
+
+ final JLabel treeNameLabel = new JLabel("Tree 1");
+ final SpinnerNumberModel spinnerModel = new SpinnerNumberModel(1, 1, 1, 1);
+ JSpinner currentTreeSpinner = new JSpinner(spinnerModel);
+
+ currentTreeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ treeViewer.showTree((Integer)spinnerModel.getValue() - 1);
+ }
+ });
+
+ treeViewer.addTreeViewerListener(new TreeViewerListener() {
+ public void treeChanged() {
+ int index = treeViewer.getCurrentTreeIndex() + 1;
+ int treeCount = treeViewer.getTrees().size();
+ if (treeCount > 0) {
+ Tree tree = treeViewer.getCurrentTree();
+ spinnerModel.setValue(index);
+ spinnerModel.setMaximum(treeCount);
+ String name = (String)tree.getAttribute("name");
+ if (name != null) {
+ treeNameLabel.setText(name);
+ } else {
+ treeNameLabel.setText("Tree " + index);
+ }
+ titleLabel.setText("Current Tree: " + index + " / " + treeCount);
+ } else {
+ titleLabel.setText("No trees");
+
+ }
+ }
+
+ public void treeSettingsChanged() {
+ // nothing to do
+ }
+ });
+ optionsPanel.addComponentWithLabel("Name:", treeNameLabel);
+ optionsPanel.addComponentWithLabel("Tree:", currentTreeSpinner);
+
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return true;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ }
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeAppearanceController.java b/src/jebl/gui/trees/treeviewer_dev/TreeAppearanceController.java
new file mode 100644
index 0000000..7597ab3
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeAppearanceController.java
@@ -0,0 +1,232 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.trees.Tree;
+import jebl.evolution.graphs.Node;
+import jebl.gui.trees.treeviewer_dev.decorators.*;
+import jebl.util.Attributable;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.*;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeAppearanceController.java 639 2007-02-15 10:05:28Z rambaut $
+ */
+public class TreeAppearanceController extends AbstractController {
+
+ private static final String CONTROLLER_TITLE = "Appearance";
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(TreeAppearanceController.class);
+
+ private static final String CONTROLLER_KEY = "appearance";
+
+ private static final String FOREGROUND_COLOUR_KEY = "foregroundColour";
+ private static final String BACKGROUND_COLOUR_KEY = "backgroundColour";
+ private static final String SELECTION_COLOUR_KEY = "selectionColour";
+ private static final String BRANCH_COLOR_ATTRIBUTE_KEY = "branchColorAttribute";
+ private static final String BRANCH_LINE_WIDTH_KEY = "branchLineWidth";
+
+ // The defaults if there is nothing in the preferences
+ private static Color DEFAULT_FOREGROUND_COLOUR = Color.BLACK;
+ private static Color DEFAULT_BACKGROUND_COLOUR = Color.WHITE;
+ private static Color DEFAULT_SELECTION_COLOUR = new Color(180, 213, 254);
+ private static float DEFAULT_BRANCH_LINE_WIDTH = 1.0f;
+
+ public TreeAppearanceController(final TreeViewer treeViewer) {
+ this.treeViewer = treeViewer;
+
+ final AttributableDecorator branchDecorator = new AttributableDecorator();
+ branchDecorator.setPaintAttributeName("!color");
+ branchDecorator.setStrokeAttributeName("!stroke");
+ treeViewer.setBranchDecorator(branchDecorator);
+
+ int foregroundRGB = TreeAppearanceController.PREFS.getInt(CONTROLLER_KEY + "." + FOREGROUND_COLOUR_KEY, DEFAULT_FOREGROUND_COLOUR.getRGB());
+ int backgroundRGB = TreeAppearanceController.PREFS.getInt(CONTROLLER_KEY + "." + BACKGROUND_COLOUR_KEY, DEFAULT_BACKGROUND_COLOUR.getRGB());
+ int selectionRGB = TreeAppearanceController.PREFS.getInt(CONTROLLER_KEY + "." + SELECTION_COLOUR_KEY, DEFAULT_SELECTION_COLOUR.getRGB());
+ float branchLineWidth = TreeAppearanceController.PREFS.getFloat(CONTROLLER_KEY + "." + BRANCH_LINE_WIDTH_KEY, DEFAULT_BRANCH_LINE_WIDTH);
+
+ treeViewer.setForeground(new Color(foregroundRGB));
+ treeViewer.setBackground(new Color(backgroundRGB));
+ treeViewer.setSelectionPaint(new Color(selectionRGB));
+ treeViewer.setBranchStroke(new BasicStroke(branchLineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ titleLabel = new JLabel(CONTROLLER_TITLE);
+
+ optionsPanel = new OptionsPanel();
+
+ branchLineWidthSpinner = new JSpinner(new SpinnerNumberModel(1.0, 0.01, 48.0, 1.0));
+
+ branchLineWidthSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ float lineWidth = ((Double) branchLineWidthSpinner.getValue()).floatValue();
+ treeViewer.setBranchStroke(new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
+ }
+ });
+ optionsPanel.addComponentWithLabel("Line Weight:", branchLineWidthSpinner);
+
+ branchColorAttributeCombo = new JComboBox(new String[] { "No attributes" });
+ setupAttributes(treeViewer.getTrees());
+ branchColorAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ if (branchColorAttributeCombo.getSelectedIndex() == 0) {
+ treeViewer.setBranchColouringDecorator(null, null);
+ treeViewer.setBranchDecorator(branchDecorator);
+ } else {
+ Set<Node> nodes = new HashSet<Node>();
+ for (Tree tree : treeViewer.getTrees()) {
+ for (Node node : tree.getNodes()) {
+ nodes.add(node);
+ }
+ }
+ String attribute = (String)branchColorAttributeCombo.getSelectedItem();
+ if (attribute != null && attribute.length() > 0) {
+ if (attribute.endsWith("*")) {
+ Decorator decorator = new DiscreteColorDecorator();
+
+ treeViewer.setBranchColouringDecorator(attribute.substring(0, attribute.length() - 2), decorator);
+ treeViewer.setBranchDecorator(null);
+ } else if (DiscreteColorDecorator.isDiscrete(attribute, nodes)) {
+ Decorator decorator = new DiscreteColorDecorator(attribute, nodes);
+
+ treeViewer.setBranchColouringDecorator(null, null);
+ treeViewer.setBranchDecorator(decorator);
+ } else {
+
+ Decorator decorator = new ContinuousColorDecorator(
+ attribute, nodes,
+ new Color(192, 16, 0), new Color(0, 16, 192));
+
+ treeViewer.setBranchColouringDecorator(null, null);
+ treeViewer.setBranchDecorator(decorator);
+ }
+ }
+ }
+ }
+ });
+
+ optionsPanel.addComponentWithLabel("Color by:", branchColorAttributeCombo);
+
+ treeViewer.addTreeViewerListener(new TreeViewerListener() {
+ public void treeChanged() {
+ setupAttributes(treeViewer.getTrees());
+ optionsPanel.repaint();
+ }
+
+ public void treeSettingsChanged() {
+ // nothing to do
+ }
+ });
+ }
+
+ private void setupAttributes(Collection<? extends Tree> trees) {
+ Object selected = branchColorAttributeCombo.getSelectedItem();
+
+ branchColorAttributeCombo.removeAllItems();
+ branchColorAttributeCombo.addItem("User Selection");
+ if (trees == null) {
+ return;
+ }
+ for (Tree tree : trees) {
+ for (String name : getAttributeNames(tree.getNodes())) {
+ branchColorAttributeCombo.addItem(name);
+ }
+ }
+ branchColorAttributeCombo.setSelectedItem(selected);
+ }
+
+ private String[] getAttributeNames(Collection<? extends Attributable> items) {
+ java.util.Set<String> attributeNames = new TreeSet<String>();
+
+ for (Attributable item : items) {
+ for (String name : item.getAttributeNames()) {
+ if (!name.startsWith("!")) {
+ Object attr = item.getAttribute(name);
+ if (!(attr instanceof Object[])) {
+ attributeNames.add(name);
+ } else {
+ boolean isColouring = true;
+
+ Object[] array = (Object[])attr;
+ boolean isIndex = true;
+ for (Object element : array) {
+ if (isIndex && !(element instanceof Integer) ||
+ !isIndex && !(element instanceof Double)) {
+ isColouring = false;
+ break;
+ }
+ isIndex = !isIndex;
+ }
+
+ if (isIndex) {
+ // a colouring should finish on an index (which means isIndex should be false)...
+ isColouring = false;
+ }
+
+ if (isColouring) {
+ attributeNames.add(name + " *");
+ }
+
+ }
+ }
+ }
+ }
+
+ String[] attributeNameArray = new String[attributeNames.size()];
+ attributeNames.toArray(attributeNameArray);
+
+ return attributeNameArray;
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ // These settings don't have controls yet but they will!
+ treeViewer.setForeground((Color)settings.get(CONTROLLER_KEY + "." + FOREGROUND_COLOUR_KEY));
+ treeViewer.setBackground((Color)settings.get(CONTROLLER_KEY + "." + BACKGROUND_COLOUR_KEY));
+ treeViewer.setSelectionPaint((Color)settings.get(CONTROLLER_KEY + "." + SELECTION_COLOUR_KEY));
+
+ branchColorAttributeCombo.setSelectedItem(settings.get(CONTROLLER_KEY+"."+BRANCH_COLOR_ATTRIBUTE_KEY));
+ branchLineWidthSpinner.setValue((Double)settings.get(CONTROLLER_KEY + "." + BRANCH_LINE_WIDTH_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ // These settings don't have controls yet but they will!
+ settings.put(CONTROLLER_KEY + "." + FOREGROUND_COLOUR_KEY, treeViewer.getForeground());
+ settings.put(CONTROLLER_KEY + "." + BACKGROUND_COLOUR_KEY, treeViewer.getBackground());
+ settings.put(CONTROLLER_KEY + "." + SELECTION_COLOUR_KEY, treeViewer.getSelectionPaint());
+
+ settings.put(CONTROLLER_KEY + "." + BRANCH_COLOR_ATTRIBUTE_KEY, branchColorAttributeCombo.getSelectedItem().toString());
+ settings.put(CONTROLLER_KEY + "." + BRANCH_LINE_WIDTH_KEY, branchLineWidthSpinner.getValue());
+ }
+
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final JComboBox branchColorAttributeCombo;
+ private final JSpinner branchLineWidthSpinner;
+
+ private final TreeViewer treeViewer;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreePane.java b/src/jebl/gui/trees/treeviewer_dev/TreePane.java
new file mode 100644
index 0000000..99464b0
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreePane.java
@@ -0,0 +1,1306 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.taxa.Taxon;
+import jebl.evolution.trees.*;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+import jebl.gui.trees.treeviewer_dev.painters.*;
+import jebl.gui.trees.treeviewer_dev.treelayouts.*;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.print.*;
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePane.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class TreePane extends JComponent implements PainterListener, Printable {
+
+ public final String CARTOON_ATTRIBUTE_NAME = "!cartoon";
+ public final String COLLAPSE_ATTRIBUTE_NAME = "!collapse";
+
+ public TreePane() {
+ }
+
+ public RootedTree getTree() {
+ return tree;
+ }
+
+ public void setTree(RootedTree tree) {
+ if (tree != null) {
+ this.originalTree = tree;
+ if (!originalTree.hasLengths()) {
+ transformBranchesOn = true;
+ }
+ setupTree();
+ } else {
+ originalTree = null;
+ this.tree = null;
+ invalidate();
+ repaint();
+ }
+ }
+
+ private void setupTree() {
+ tree = originalTree;
+
+ if (orderBranchesOn) {
+ tree = new SortedRootedTree(tree, branchOrdering);
+ }
+
+ if (transformBranchesOn || !this.tree.hasLengths()) {
+ tree = new TransformedRootedTree(tree, branchTransform);
+ }
+
+ calibrated = false;
+ invalidate();
+ repaint();
+ }
+
+ public TreeLayout getTreeLayout() {
+ return treeLayout;
+ }
+
+ public TreeLayoutCache getTreeLayoutCache() {
+ return treeLayoutCache;
+ }
+
+ public void setTreeLayout(TreeLayout treeLayout) {
+
+ this.treeLayout = treeLayout;
+
+ treeLayout.setCartoonAttributeName(CARTOON_ATTRIBUTE_NAME);
+ treeLayout.setCollapsedAttributeName(COLLAPSE_ATTRIBUTE_NAME);
+ treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
+
+ treeLayout.addTreeLayoutListener(new TreeLayoutListener() {
+ public void treeLayoutChanged() {
+ calibrated = false;
+ repaint();
+ }
+ });
+ calibrated = false;
+ invalidate();
+ repaint();
+ }
+
+ public void setBranchDecorator(Decorator branchDecorator) {
+ this.branchDecorator = branchDecorator;
+ repaint();
+ }
+
+ public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) {
+ this.branchColouringAttribute = branchColouringAttribute;
+ treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
+ this.branchColouringDecorator = branchColouringDecorator;
+ repaint();
+ }
+
+ public Rectangle2D getTreeBounds() {
+ return treeBounds;
+ }
+
+ /**
+ * This returns the scaling factor between the graphical image and the branch
+ * lengths of the tree
+ *
+ * @return the tree scale
+ */
+ public double getTreeScale() {
+ return treeScale;
+ }
+
+ public void painterChanged() {
+ calibrated = false;
+ repaint();
+ }
+
+ public void painterSettingsChanged() {
+ calibrated = false;
+ repaint();
+ }
+
+ public BasicStroke getBranchStroke() {
+ return branchLineStroke;
+ }
+
+ public void setBranchStroke(BasicStroke stroke) {
+ branchLineStroke = stroke;
+ float weight = stroke.getLineWidth();
+ selectionStroke = new BasicStroke(Math.max(weight + 4.0F, weight * 1.5F), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ repaint();
+ }
+
+ public BasicStroke getCalloutStroke() {
+ return calloutStroke;
+ }
+
+ public void setCalloutStroke(BasicStroke calloutStroke) {
+ this.calloutStroke = calloutStroke;
+ }
+
+ public Paint getSelectionPaint() {
+ return selectionPaint;
+ }
+
+ public void setSelectionPaint(Paint selectionPaint) {
+ this.selectionPaint = selectionPaint;
+ }
+
+ public boolean isTransformBranchesOn() {
+ return transformBranchesOn;
+ }
+
+ public void setTransformBranchesOn(boolean transformBranchesOn) {
+ this.transformBranchesOn = transformBranchesOn;
+ setupTree();
+ }
+
+ public TransformedRootedTree.Transform getBranchTransform() {
+ return branchTransform;
+ }
+
+ public void setBranchTransform(TransformedRootedTree.Transform branchTransform) {
+ this.branchTransform = branchTransform;
+ setupTree();
+ }
+
+ public boolean isOrderBranchesOn() {
+ return orderBranchesOn;
+ }
+
+ public void setOrderBranchesOn(boolean orderBranchesOn) {
+ this.orderBranchesOn = orderBranchesOn;
+ setupTree();
+ }
+
+ public SortedRootedTree.BranchOrdering getBranchOrdering() {
+ return branchOrdering;
+ }
+
+ public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) {
+ this.branchOrdering = branchOrdering;
+ setupTree();
+ }
+
+ public RootedTree getOriginalTree() {
+ return originalTree;
+ }
+
+ public boolean isShowingTipCallouts() {
+ return showingTipCallouts;
+ }
+
+ public void setShowingTipCallouts(boolean showingTipCallouts) {
+ this.showingTipCallouts = showingTipCallouts;
+ calibrated = false;
+ repaint();
+ }
+
+ public void setSelectedNode(Node selectedNode) {
+ selectedNodes.clear();
+ selectedTips.clear();
+ addSelectedNode(selectedNode);
+ }
+
+ public void setSelectedTip(Node selectedTip) {
+ selectedNodes.clear();
+ selectedTips.clear();
+ addSelectedTip(selectedTip);
+ }
+
+ public void setSelectedClade(Node selectedNode) {
+ selectedNodes.clear();
+ selectedTips.clear();
+ addSelectedClade(selectedNode);
+ }
+
+ public void setSelectedTips(Node selectedNode) {
+ selectedNodes.clear();
+ selectedTips.clear();
+ addSelectedTips(selectedNode);
+ }
+
+ private boolean canSelectNode(Node selectedNode) {
+ return selectedNode != null;
+ }
+ public void addSelectedNode(Node selectedNode) {
+ if ( canSelectNode(selectedNode) ) {
+ selectedNodes.add(selectedNode);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void addSelectedTip(Node selectedTip) {
+ if (selectedTip != null) {
+ this.selectedTips.add(selectedTip);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void addSelectedClade(Node selectedNode) {
+ if ( canSelectNode(selectedNode) ) {
+ addSelectedChildClades(selectedNode);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ private void addSelectedChildClades(Node selectedNode) {
+ selectedNodes.add(selectedNode);
+ for (Node child : tree.getChildren(selectedNode)) {
+ addSelectedChildClades(child);
+ }
+ }
+
+ public void addSelectedTips(Node selectedNode) {
+ if (selectedNode != null) {
+ addSelectedChildTips(selectedNode);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ private void addSelectedChildTips(Node selectedNode) {
+ if (tree.isExternal(selectedNode)) {
+ selectedTips.add(selectedNode);
+ }
+ for (Node child : tree.getChildren(selectedNode)) {
+ addSelectedChildTips(child);
+ }
+ }
+
+ public void selectCladesFromSelectedNodes() {
+ Set<Node> nodes = new HashSet<Node>(selectedNodes);
+ selectedNodes.clear();
+ for (Node node : nodes) {
+ addSelectedClade(node);
+ }
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void selectTipsFromSelectedNodes() {
+ for (Node node : selectedNodes) {
+ addSelectedChildTips(node);
+ }
+ selectedNodes.clear();
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void selectNodesFromSelectedTips() {
+ if (selectedTips.size() > 0) {
+ Node node = RootedTreeUtils.getCommonAncestorNode(tree, selectedTips);
+ addSelectedClade(node);
+ }
+
+ selectedTips.clear();
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void selectAllTaxa() {
+ selectedTips.addAll(tree.getExternalNodes());
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void selectAllNodes() {
+ selectedNodes.addAll(tree.getNodes());
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public void clearSelection() {
+ selectedNodes.clear();
+ selectedTips.clear();
+ fireSelectionChanged();
+ repaint();
+ }
+
+ public boolean hasSelection() {
+ return selectedNodes.size() > 0 || selectedTips.size() > 0;
+ }
+
+ public void cartoonSelectedNodes() {
+ cartoonSelectedNodes(tree.getRootNode());
+ }
+
+ private void cartoonSelectedNodes(Node node) {
+
+ if (!tree.isExternal(node)) {
+ if (selectedNodes.contains(node)) {
+ if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
+ node.removeAttribute(CARTOON_ATTRIBUTE_NAME);
+ } else {
+ int tipCount = RootedTreeUtils.getTipCount(tree, node);
+ double height = RootedTreeUtils.getMinTipHeight(tree, node);
+ Object[] values = new Object[] { tipCount, height };
+ node.setAttribute(CARTOON_ATTRIBUTE_NAME, values);
+ }
+ calibrated = false;
+ repaint();
+ } else {
+ for (Node child : tree.getChildren(node)) {
+ cartoonSelectedNodes(child);
+ }
+ }
+ }
+ }
+
+ public void collapseSelectedNodes() {
+ collapseSelectedNodes(tree.getRootNode());
+ }
+
+ private void collapseSelectedNodes(Node node) {
+
+ if (!tree.isExternal(node)) {
+ if (selectedNodes.contains(node)) {
+ if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
+ node.removeAttribute(COLLAPSE_ATTRIBUTE_NAME);
+ } else {
+ String tipName = "collapsed";
+ double height = RootedTreeUtils.getMinTipHeight(tree, node);
+ Object[] values = new Object[] { tipName, height };
+ node.setAttribute(COLLAPSE_ATTRIBUTE_NAME, values);
+ }
+ calibrated = false;
+ repaint();
+ } else {
+ for (Node child : tree.getChildren(node)) {
+ collapseSelectedNodes(child);
+ }
+ }
+ }
+ }
+
+ public void annotateSelectedNodes(String name, Object value) {
+ for (Node selectedNode : selectedNodes) {
+ selectedNode.setAttribute(name, value);
+ }
+ repaint();
+ }
+
+ public void annotateSelectedTips(String name, Object value) {
+ for (Node selectedTip : selectedTips) {
+ Taxon selectedTaxon = tree.getTaxon(selectedTip);
+ selectedTaxon.setAttribute(name, value);
+ }
+ repaint();
+ }
+
+ /**
+ * Return whether the two axis scales should be maintained
+ * relative to each other
+ *
+ * @return a boolean
+ */
+ public boolean maintainAspectRatio() {
+ return treeLayout.maintainAspectRatio();
+ }
+
+ public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) {
+ tipLabelPainter.setTreePane(this);
+ if (this.tipLabelPainter != null) {
+ this.tipLabelPainter.removePainterListener(this);
+ }
+ this.tipLabelPainter = tipLabelPainter;
+ if (this.tipLabelPainter != null) {
+ this.tipLabelPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public LabelPainter<Node> getTipLabelPainter() {
+ return tipLabelPainter;
+ }
+
+ public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) {
+ nodeLabelPainter.setTreePane(this);
+ if (this.nodeLabelPainter != null) {
+ this.nodeLabelPainter.removePainterListener(this);
+ }
+ this.nodeLabelPainter = nodeLabelPainter;
+ if (this.nodeLabelPainter != null) {
+ this.nodeLabelPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public LabelPainter<Node> getNodeLabelPainter() {
+ return nodeLabelPainter;
+ }
+
+ public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) {
+ branchLabelPainter.setTreePane(this);
+ if (this.branchLabelPainter != null) {
+ this.branchLabelPainter.removePainterListener(this);
+ }
+ this.branchLabelPainter = branchLabelPainter;
+ if (this.branchLabelPainter != null) {
+ this.branchLabelPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public LabelPainter<Node> getBranchLabelPainter() {
+ return branchLabelPainter;
+ }
+
+ public void setNodeBarPainter(NodeBarPainter nodeBarPainter) {
+ nodeBarPainter.setTreePane(this);
+ if (this.nodeBarPainter != null) {
+ this.nodeBarPainter.removePainterListener(this);
+ }
+ this.nodeBarPainter = nodeBarPainter;
+ if (this.nodeBarPainter != null) {
+ this.nodeBarPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public NodeBarPainter getNodeBarPainter() {
+ return nodeBarPainter;
+ }
+
+ //TEST
+ public void setNodeHistPainter(NodeHistPainter nodeHistPainter) {
+ nodeHistPainter.setTreePane(this);
+ if (this.nodeHistPainter != null) {
+ this.nodeHistPainter.removePainterListener(this);
+ }
+ this.nodeHistPainter = nodeHistPainter;
+ if (this.nodeHistPainter != null) {
+ this.nodeHistPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public NodeHistPainter getNodeHistPainter() {
+ return nodeHistPainter;
+ }
+ //END TEST
+
+ public void setScaleBarPainter(Painter<TreePane> scaleBarPainter) {
+ scaleBarPainter.setTreePane(this);
+ if (this.scaleBarPainter != null) {
+ this.scaleBarPainter.removePainterListener(this);
+ }
+ this.scaleBarPainter = scaleBarPainter;
+ if (this.scaleBarPainter != null) {
+ this.scaleBarPainter.addPainterListener(this);
+ }
+ calibrated = false;
+ repaint();
+ }
+
+ public Painter<TreePane> getScaleBarPainter() {
+ return scaleBarPainter;
+ }
+
+ public void setPreferredSize(Dimension dimension) {
+ if (treeLayout.maintainAspectRatio()) {
+ super.setPreferredSize(new Dimension(dimension.width, dimension.height));
+ } else {
+ super.setPreferredSize(dimension);
+ }
+
+ calibrated = false;
+ }
+
+ public double getHeightAt(Graphics2D graphics2D, Point2D point) {
+ try {
+ point = transform.inverseTransform(point, null);
+ } catch (NoninvertibleTransformException e) {
+ e.printStackTrace();
+ }
+ return treeLayout.getHeightOfPoint(point);
+ }
+
+ public Node getNodeAt(Graphics2D g2, Point point) {
+ Rectangle rect = new Rectangle(point.x - 1, point.y - 1, 3, 3);
+
+ for (Node node : tree.getExternalNodes()) {
+ Shape taxonLabelBound = tipLabelBounds.get(node);
+
+ if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
+ return node;
+ }
+ }
+
+ for (Node node : tree.getNodes()) {
+ Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
+ if (branchPath != null && g2.hit(rect, branchPath, true)) {
+ return node;
+ }
+ Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
+ if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
+ return node;
+ }
+ }
+
+ return null;
+ }
+
+ public Set<Node> getNodesAt(Graphics2D g2, Rectangle rect) {
+
+ Set<Node> nodes = new HashSet<Node>();
+ for (Node node : tree.getExternalNodes()) {
+ Shape taxonLabelBound = tipLabelBounds.get(node);
+ if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
+ nodes.add(node);
+ }
+ }
+
+ for (Node node : tree.getNodes()) {
+ Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
+ if (branchPath != null && g2.hit(rect, branchPath, true)) {
+ nodes.add(node);
+ }
+ Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
+ if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
+ nodes.add(node);
+ }
+ }
+
+ return nodes;
+ }
+
+ public Set<Node> getSelectedNodes() {
+ return selectedNodes;
+ }
+
+ public Set<Node> getSelectedTips() {
+ return selectedTips;
+ }
+
+ public Rectangle2D getDragRectangle() {
+ return dragRectangle;
+ }
+
+ public void setDragRectangle(Rectangle2D dragRectangle) {
+ this.dragRectangle = dragRectangle;
+ repaint();
+ }
+
+ public void setRuler(double rulerHeight) {
+ this.rulerHeight = rulerHeight;
+ }
+
+ public void scrollPointToVisible(Point point) {
+ scrollRectToVisible(new Rectangle(point.x, point.y, 0, 0));
+ }
+
+
+ private final Set<TreeSelectionListener> treeSelectionListeners = new HashSet<TreeSelectionListener>();
+
+ public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treeSelectionListeners.add(treeSelectionListener);
+ }
+
+ public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
+ treeSelectionListeners.remove(treeSelectionListener);
+ }
+
+ private void fireSelectionChanged() {
+ for (TreeSelectionListener treeSelectionListener : treeSelectionListeners) {
+ treeSelectionListener.selectionChanged();
+ }
+ }
+
+ public void paint(Graphics graphics) {
+ if (tree == null) return;
+
+ final Graphics2D g2 = (Graphics2D) graphics;
+ if (!calibrated) calibrate(g2, getWidth(), getHeight());
+
+ Paint oldPaint = g2.getPaint();
+ Stroke oldStroke = g2.getStroke();
+
+ for (Node selectedNode : selectedNodes) {
+ Shape branchPath = treeLayoutCache.getBranchPath(selectedNode);
+ if (branchPath != null) {
+ Shape transPath = transform.createTransformedShape(branchPath);
+ g2.setPaint(selectionPaint);
+ g2.setStroke(selectionStroke);
+ g2.draw(transPath);
+ }
+ Shape collapsedShape = treeLayoutCache.getCollapsedShape(selectedNode);
+ if (collapsedShape != null) {
+ Shape transPath = transform.createTransformedShape(collapsedShape);
+ g2.setPaint(selectionPaint);
+ g2.setStroke(selectionStroke);
+ g2.draw(transPath);
+ }
+ }
+
+ for (Node selectedTip : selectedTips) {
+ g2.setPaint(selectionPaint);
+ Shape labelBounds = tipLabelBounds.get(selectedTip);
+ if (labelBounds != null) {
+ g2.fill(labelBounds);
+ }
+ }
+
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+
+ drawTree(g2, getWidth(), getHeight());
+
+ if (dragRectangle != null) {
+ g2.setPaint(new Color(128, 128, 128, 128));
+ g2.fill(dragRectangle);
+
+ g2.setStroke(new BasicStroke(2.0F));
+ g2.setPaint(new Color(255, 255, 255, 128));
+ g2.draw(dragRectangle);
+
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+ }
+
+ }
+
+ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
+
+ if (tree == null || pageIndex > 0) return NO_SUCH_PAGE;
+
+ Graphics2D g2 = (Graphics2D) graphics;
+ g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+
+ calibrated = false;
+ setDoubleBuffered(false);
+
+ drawTree(g2, pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
+
+ setDoubleBuffered(true);
+ calibrated = false;
+
+ return PAGE_EXISTS;
+ }
+
+ public void drawTree(Graphics2D g2, double width, double height) {
+
+ if (!calibrated) calibrate(g2, width, height);
+
+ AffineTransform oldTransform = g2.getTransform();
+ Paint oldPaint = g2.getPaint();
+ Stroke oldStroke = g2.getStroke();
+ Font oldFont = g2.getFont();
+
+ g2.setStroke(branchLineStroke);
+
+ // Paint collapsed nodes
+ for (Node node : treeLayoutCache.getCollapsedShapeMap().keySet() ) {
+ Shape collapsedShape = treeLayoutCache.getCollapsedShape(node);
+
+ Shape transShape = transform.createTransformedShape(collapsedShape);
+ Paint paint = Color.BLACK;
+ Paint fillPaint = null;
+ if (branchDecorator != null) {
+ branchDecorator.setItem(node);
+ paint = branchDecorator.getPaint(paint);
+ fillPaint = branchDecorator.getFillPaint(fillPaint);
+ }
+
+ if (fillPaint != null) {
+ g2.setPaint(fillPaint);
+ g2.fill(transShape);
+ }
+
+ g2.setPaint(paint);
+ g2.draw(transShape);
+ }
+
+ // Paint branches
+ for (Node node : treeLayoutCache.getBranchPathMap().keySet() ) {
+
+ Object[] branchColouring = null;
+ if (treeLayout.isShowingColouring() && branchColouringAttribute != null) {
+ branchColouring = (Object[])node.getAttribute(branchColouringAttribute);
+ }
+
+ Shape branchPath = treeLayoutCache.getBranchPath(node);
+
+ if (branchColouring != null) {
+ PathIterator iter = branchPath.getPathIterator(transform);
+
+ float[] coords1 = new float[2];
+ iter.currentSegment(coords1);
+
+ for (int i = 0; i < branchColouring.length - 1; i+=2) {
+ iter.next();
+ float[] coords2 = new float[2];
+ iter.currentSegment(coords2);
+
+ int colour = ((Number)branchColouring[i]).intValue();
+ branchColouringDecorator.setItem(colour);
+ g2.setPaint(branchColouringDecorator.getPaint(Color.BLACK));
+ g2.draw(new Line2D.Float(coords1[0], coords1[1], coords2[0], coords2[1]));
+
+ coords1 = coords2;
+ }
+
+ // Draw the remaining branch as a path so it has proper line joins...
+ int colour = ((Number)branchColouring[branchColouring.length - 1]).intValue();
+ branchColouringDecorator.setItem(colour);
+ g2.setPaint(branchColouringDecorator.getPaint(Color.BLACK));
+
+ GeneralPath path = new GeneralPath();
+ path.moveTo(coords1[0], coords1[1]);
+ iter.next();
+ while (!iter.isDone()) {
+ iter.currentSegment(coords1);
+ path.lineTo(coords1[0], coords1[1]);
+ iter.next();
+ }
+ g2.draw(path);
+
+ } else {
+ Shape transPath = transform.createTransformedShape(branchPath);
+ if (branchDecorator != null) {
+ branchDecorator.setItem(node);
+ g2.setPaint(branchDecorator.getPaint(Color.BLACK));
+ } else {
+ g2.setPaint(Color.BLACK);
+ }
+ g2.draw(transPath);
+ }
+ }
+
+ // Paint node bars
+ if (nodeBarPainter != null && nodeBarPainter.isVisible()) {
+ for (Node node : nodeBars.keySet() ) {
+ Shape nodeBar = nodeBars.get(node);
+ nodeBar = transform.createTransformedShape(nodeBar);
+ nodeBarPainter.paint(g2, node, NodePainter.Justification.CENTER, nodeBar);
+ }
+ }
+
+ // Paint tip labels
+ if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
+
+ for (Node node : tipLabelTransforms.keySet()) {
+
+ AffineTransform tipLabelTransform = tipLabelTransforms.get(node);
+
+ Painter.Justification tipLabelJustification = tipLabelJustifications.get(node);
+ g2.transform(tipLabelTransform);
+
+ tipLabelPainter.paint(g2, node, tipLabelJustification,
+ new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, tipLabelPainter.getPreferredHeight()));
+
+ g2.setTransform(oldTransform);
+
+ if (showingTipCallouts) {
+ Shape calloutPath = transform.createTransformedShape(treeLayoutCache.getCalloutPath(node));
+ if (calloutPath != null) {
+ g2.setStroke(calloutStroke);
+ g2.draw(calloutPath);
+ }
+ }
+ }
+ }
+
+ // Paint node labels
+ if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
+ for (Node node : nodeLabelTransforms.keySet() ) {
+
+ AffineTransform nodeTransform = nodeLabelTransforms.get(node);
+
+ Painter.Justification nodeLabelJustification = nodeLabelJustifications.get(node);
+ g2.transform(nodeTransform);
+
+ nodeLabelPainter.paint(g2, node, nodeLabelJustification,
+ new Rectangle2D.Double(0.0, 0.0, nodeLabelPainter.getPreferredWidth(), nodeLabelPainter.getPreferredHeight()));
+
+ g2.setTransform(oldTransform);
+ }
+ }
+
+ // Paint branch labels
+ if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
+
+ for (Node node : branchLabelTransforms.keySet() ) {
+
+ AffineTransform branchTransform = branchLabelTransforms.get(node);
+
+ g2.transform(branchTransform);
+
+ branchLabelPainter.calibrate(g2, node);
+ final double preferredWidth = branchLabelPainter.getPreferredWidth();
+ final double preferredHeight = branchLabelPainter.getPreferredHeight();
+
+ //Line2D labelPath = treeLayout.getBranchLabelPath(node);
+
+ branchLabelPainter.paint(g2, node, Painter.Justification.CENTER,
+ //new Rectangle2D.Double(-preferredWidth/2, -preferredHeight, preferredWidth, preferredHeight));
+ new Rectangle2D.Double(0, 0, preferredWidth, preferredHeight));
+
+ g2.setTransform(oldTransform);
+ }
+ }
+
+ // Paint scale bar
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.paint(g2, this, Painter.Justification.CENTER, scaleBarBounds);
+ }
+
+ g2.setStroke(oldStroke);
+ g2.setPaint(oldPaint);
+ g2.setFont(oldFont);
+ }
+
+ private void calibrate(Graphics2D g2, double width, double height) {
+
+ treeLayout.layout(tree, treeLayoutCache);
+
+ // First of all get the bounds for the unscaled tree
+ treeBounds = null;
+ boolean showingRootBranch = treeLayout.isShowingRootBranch();
+
+ Node rootNode = tree.getRootNode();
+
+ // There are two sets of bounds here. The treeBounds are the bounds of the elements
+ // that make up the actual tree. These are scaled from branch length space
+
+ // The bounds are then the extra stuff that doesn't get scaled with the tree such
+ // as labels and the like.
+
+ // bounds on branches
+ for (Shape branchPath : treeLayoutCache.getBranchPathMap().values()) {
+ // Add the bounds of the branch path to the overall bounds
+ final Rectangle2D branchBounds = branchPath.getBounds2D();
+ if (treeBounds == null) {
+ treeBounds = branchBounds;
+ } else {
+ treeBounds.add(branchBounds);
+ }
+ }
+
+ for (Shape collapsedShape : treeLayoutCache.getCollapsedShapeMap().values()) {
+ // Add the bounds of the branch path to the overall bounds
+ final Rectangle2D branchBounds = collapsedShape.getBounds2D();
+ if (treeBounds == null) {
+ treeBounds = branchBounds;
+ } else {
+ treeBounds.add(branchBounds);
+ }
+ }
+
+ // bounds on nodeShapes
+ if (nodeBarPainter != null && nodeBarPainter.isVisible()) {
+ nodeBars.clear();
+ // Iterate though the nodes
+ for (Node node : tree.getInternalNodes()) {
+
+ Rectangle2D shapeBounds = nodeBarPainter.calibrate(g2, node);
+ treeBounds.add(shapeBounds);
+ nodeBars.put(node, nodeBarPainter.getNodeBar());
+ }
+ }
+
+ // adjust the bounds so that the origin is at 0,0
+ //treeBounds = new Rectangle2D.Double(0.0, 0.0, treeBounds.getWidth(), treeBounds.getHeight());
+
+ // add the tree bounds
+ final Rectangle2D bounds = treeBounds.getBounds2D(); // (YH) same as (Rectangle2D) treeBounds.clone();
+
+ final Set<Node> externalNodes = tree.getExternalNodes();
+
+ if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
+
+ tipLabelWidth = 0.0;
+
+ // Find the longest taxon label
+ for (Node node : externalNodes) {
+
+ tipLabelPainter.calibrate(g2, node);
+ tipLabelWidth = Math.max(tipLabelWidth, tipLabelPainter.getPreferredWidth());
+ }
+
+ final double tipLabelHeight = tipLabelPainter.getPreferredHeight();
+
+ // Iterate though the nodes with tip labels
+ for (Node node : treeLayoutCache.getTipLabelPathMap().keySet()) {
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, tipLabelHeight);
+
+ // Get the line that represents the path for the taxon label
+ Line2D taxonPath = treeLayoutCache.getTipLabelPath(node);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform taxonTransform = calculateTransform(null, taxonPath, tipLabelWidth, tipLabelHeight, true);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(taxonTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+
+ if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
+ // Iterate though the nodes with node labels
+ for (Node node : treeLayoutCache.getNodeLabelPathMap().keySet()) {
+ // Get the line that represents the path for the taxon label
+ final Line2D labelPath = treeLayoutCache.getNodeLabelPath(node);
+
+ nodeLabelPainter.calibrate(g2, node);
+ final double labelHeight = nodeLabelPainter.getPreferredHeight();
+ final double labelWidth = nodeLabelPainter.getPreferredWidth();
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, true);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+
+ if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
+ // Iterate though the nodes with branch labels
+ for (Node node : treeLayoutCache.getBranchLabelPathMap().keySet()) {
+ // Get the line that represents the path for the branch label
+ final Line2D labelPath = treeLayoutCache.getBranchLabelPath(node);
+
+ branchLabelPainter.calibrate(g2, node);
+ final double labelHeight = branchLabelPainter.getHeightBound();
+ final double labelWidth = branchLabelPainter.getPreferredWidth();
+
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, false);
+
+ // and add the translated bounds to the overall bounds
+ bounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
+ }
+ }
+
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.calibrate(g2, this);
+ scaleBarBounds = new Rectangle2D.Double(treeBounds.getX(), treeBounds.getY(),
+ treeBounds.getWidth(), scaleBarPainter.getPreferredHeight());
+ bounds.add(scaleBarBounds);
+ }
+
+ final double avilableW = width - insets.left - insets.right;
+ final double avaiableH = height - insets.top - insets.bottom;
+
+ // get the difference between the tree's bounds and the overall bounds
+
+ double xDiff = bounds.getWidth() - treeBounds.getWidth();
+ double yDiff = bounds.getHeight() - treeBounds.getHeight();
+ assert xDiff >= 0 && yDiff >= 0;
+
+ // small tree, long labels, label bounds may get larger that window, protect against that
+
+ if( xDiff >= avilableW ) {
+ xDiff = Math.min(avilableW, bounds.getWidth()) - treeBounds.getWidth();
+ }
+
+ if( yDiff >= avaiableH ) {
+ yDiff = Math.min(avaiableH, bounds.getHeight()) - treeBounds.getHeight();
+ }
+ // Get the amount of canvas that is going to be taken up by the tree -
+ // The rest is taken up by taxon labels which don't scale
+
+ final double w = avilableW - xDiff;
+ final double h = avaiableH - yDiff;
+
+ double xScale;
+ double yScale;
+
+ double xOffset = 0.0;
+ double yOffset = 0.0;
+
+ if (treeLayout.maintainAspectRatio()) {
+ // If the tree is layed out in both dimensions then we
+ // need to find out which axis has the least space and scale
+ // the tree to that (to keep the aspect ratio.
+ if ((w / treeBounds.getWidth()) < (h / treeBounds.getHeight())) {
+ xScale = w / treeBounds.getWidth();
+ yScale = xScale;
+ } else {
+ yScale = h / treeBounds.getHeight();
+ xScale = yScale;
+ }
+
+ treeScale = xScale; assert treeScale > 0;
+
+ // and set the origin so that the center of the tree is in
+ // the center of the canvas
+ xOffset = ((width - (treeBounds.getWidth() * xScale)) / 2) - (treeBounds.getX() * xScale);
+ yOffset = ((height - (treeBounds.getHeight() * yScale)) / 2) - (treeBounds.getY() * yScale);
+
+ } else {
+ // Otherwise just scale both dimensions
+ xScale = w / treeBounds.getWidth();
+ yScale = h / treeBounds.getHeight();
+
+ // and set the origin in the top left corner
+ xOffset = - (treeBounds.getX() * xScale);
+ yOffset = - bounds.getY();
+
+ treeScale = xScale; assert treeScale > 0;
+ }
+
+ // Create the overall transform
+ transform = new AffineTransform();
+ transform.translate(xOffset + insets.left, yOffset + insets.top);
+ transform.scale(xScale, yScale);
+
+ // Get the bounds for the newly scaled tree
+ treeBounds = null;
+
+ // bounds on branches
+ for (Shape branchPath : treeLayoutCache.getBranchPathMap().values()) {
+ // Add the bounds of the branch path to the overall bounds
+ final Rectangle2D branchBounds = transform.createTransformedShape(branchPath).getBounds2D();
+ if (treeBounds == null) {
+ treeBounds = branchBounds;
+ } else {
+ treeBounds.add(branchBounds);
+ }
+ }
+
+ // Clear the map of individual taxon label bounds and transforms
+ tipLabelBounds.clear();
+ tipLabelTransforms.clear();
+ tipLabelJustifications.clear();
+
+ if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
+ final double labelHeight = tipLabelPainter.getPreferredHeight();
+ Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, labelHeight);
+
+ // Iterate though the external nodes with tip labels
+ for (Node node : treeLayoutCache.getTipLabelPathMap().keySet()) {
+ // Get the line that represents the path for the tip label
+ Line2D tipPath = treeLayoutCache.getTipLabelPath(node);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform taxonTransform = calculateTransform(transform, tipPath, tipLabelWidth, labelHeight, true);
+
+ // Store the transformed bounds in the map for use when selecting
+ tipLabelBounds.put(node, taxonTransform.createTransformedShape(labelBounds));
+
+ // Store the transform in the map for use when drawing
+ tipLabelTransforms.put(node, taxonTransform);
+
+ // Store the alignment in the map for use when drawing
+ final Painter.Justification just = (tipPath.getX1() < tipPath.getX2()) ?
+ Painter.Justification.LEFT : Painter.Justification.RIGHT;
+ tipLabelJustifications.put(node, just);
+ }
+ }
+
+ // Clear the map of individual node label bounds and transforms
+ nodeLabelBounds.clear();
+ nodeLabelTransforms.clear();
+ nodeLabelJustifications.clear();
+
+ if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
+ final double labelHeight = nodeLabelPainter.getPreferredHeight();
+ final double labelWidth = nodeLabelPainter.getPreferredWidth();
+ final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ // Iterate though the external nodes with node labels
+ for (Node node : treeLayoutCache.getNodeLabelPathMap().keySet()) {
+ // Get the line that represents the path for the node label
+ final Line2D labelPath = treeLayoutCache.getNodeLabelPath(node);
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, true);
+
+ // Store the transformed bounds in the map for use when selecting
+ nodeLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
+
+ // Store the transform in the map for use when drawing
+ nodeLabelTransforms.put(node, labelTransform);
+
+ // Store the alignment in the map for use when drawing
+ if (labelPath.getX1() < labelPath.getX2()) {
+ nodeLabelJustifications.put(node, Painter.Justification.LEFT);
+ } else {
+ nodeLabelJustifications.put(node, Painter.Justification.RIGHT);
+ }
+ }
+ }
+
+ branchLabelBounds.clear();
+ branchLabelTransforms.clear();
+ branchLabelJustifications.clear();
+
+ if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
+
+ // Iterate though the external nodes with branch labels
+ for (Node node : treeLayoutCache.getBranchLabelPathMap().keySet()) {
+
+ // Get the line that represents the path for the branch label
+ Line2D labelPath = treeLayoutCache.getBranchLabelPath(node);
+
+ // AR - I don't think we need to recalibrate this here
+ // branchLabelPainter.calibrate(g2, node);
+ final double labelHeight = branchLabelPainter.getPreferredHeight();
+ final double labelWidth = branchLabelPainter.getPreferredWidth();
+ final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
+
+ final double dx = labelPath.getP2().getX() - labelPath.getP1().getX();
+ final double dy = labelPath.getP2().getY() - labelPath.getP1().getY();
+ final double branchLength = Math.sqrt(dx*dx + dy*dy);
+
+ final Painter.Justification just = labelPath.getX1() < labelPath.getX2() ? Painter.Justification.LEFT :
+ Painter.Justification.RIGHT;
+
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, false);
+ // move to middle of branch - since the move is before the rotation
+ final double direction = just == Painter.Justification.RIGHT ? 1 : -1;
+ labelTransform.translate(-direction * xScale * branchLength /2, 0);
+
+ // Store the transformed bounds in the map for use when selecting
+ branchLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
+
+ // Store the transform in the map for use when drawing
+ branchLabelTransforms.put(node, labelTransform);
+
+ // Store the alignment in the map for use when drawing
+ branchLabelJustifications.put(node, just);
+ }
+ }
+
+ if (scaleBarPainter != null && scaleBarPainter.isVisible()) {
+ scaleBarPainter.calibrate(g2, this);
+ final double h1 = scaleBarPainter.getPreferredHeight();
+ scaleBarBounds = new Rectangle2D.Double(treeBounds.getX(), height - h1, treeBounds.getWidth(), h1);
+ }
+
+ calloutPaths.clear();
+
+ calibrated = true;
+ }
+
+ private AffineTransform calculateTransform(AffineTransform globalTransform, Line2D line, double width, double height, boolean just) {
+ // Work out how it is rotated and create a transform that matches that
+ AffineTransform lineTransform = new AffineTransform();
+
+ final Point2D origin = line.getP1();
+ if (globalTransform != null) {
+ globalTransform.transform(origin, origin);
+ }
+
+ final double dx = line.getX2() - line.getX1();
+ final double angle = dx != 0.0 ? Math.atan((line.getY2() - line.getY1()) / dx) : 0.0;
+ lineTransform.rotate(angle, origin.getX(), origin.getY());
+
+ // Now add a translate to the transform - if it is on the left then we need
+ // to shift it by the entire width of the string.
+ final double ty = origin.getY() - (height / 2.0);
+ if (!just || line.getX2() > line.getX1()) {
+ lineTransform.translate(origin.getX() + labelXOffset, ty);
+ } else {
+ lineTransform.translate(origin.getX() - (labelXOffset + width), ty);
+ }
+
+ return lineTransform;
+ }
+
+ // Overridden methods to recalibrate tree when bounds change
+ public void setBounds(int x, int y, int width, int height) {
+ calibrated = false;
+ super.setBounds(x, y, width, height);
+ }
+
+ public void setBounds(Rectangle rectangle) {
+ calibrated = false;
+ super.setBounds(rectangle);
+ }
+
+ public void setSize(Dimension dimension) {
+ calibrated = false;
+ super.setSize(dimension);
+ }
+
+ public void setSize(int width, int height) {
+ calibrated = false;
+ super.setSize(width, height);
+ }
+
+ private RootedTree originalTree = null;
+ private RootedTree tree = null;
+ private TreeLayout treeLayout = null;
+ private TreeLayoutCache treeLayoutCache = new TreeLayoutCache();
+
+ private boolean orderBranchesOn = false;
+ private SortedRootedTree.BranchOrdering branchOrdering = SortedRootedTree.BranchOrdering.INCREASING_NODE_DENSITY;
+
+ private boolean transformBranchesOn = false;
+ private TransformedRootedTree.Transform branchTransform = TransformedRootedTree.Transform.CLADOGRAM;
+
+ private Rectangle2D treeBounds = new Rectangle2D.Double();
+ private double treeScale;
+
+ //private Insets margins = new Insets(6, 6, 6, 6);
+ private Insets insets = new Insets(6, 6, 6, 6);
+
+ private Set<Node> selectedNodes = new HashSet<Node>();
+ private Set<Node> selectedTips = new HashSet<Node>();
+
+ private double rulerHeight = -1.0;
+ private Rectangle2D dragRectangle = null;
+
+ private Decorator branchDecorator = null;
+ private Decorator branchColouringDecorator = null;
+ private String branchColouringAttribute = null;
+
+ private float labelXOffset = 5.0F;
+ private LabelPainter<Node> tipLabelPainter = null;
+ private double tipLabelWidth;
+ private LabelPainter<Node> nodeLabelPainter = null;
+ private LabelPainter<Node> branchLabelPainter = null;
+
+ private NodeBarPainter nodeBarPainter = null;
+
+ //TEST
+ private NodeHistPainter nodeHistPainter = null;
+ //END TEST
+
+ private Painter<TreePane> scaleBarPainter = null;
+ private Rectangle2D scaleBarBounds = null;
+
+ private BasicStroke branchLineStroke = new BasicStroke(1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ private BasicStroke calloutStroke = new BasicStroke(0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{0.5f, 2.0f}, 0.0f);
+ private Stroke selectionStroke = new BasicStroke(6.0F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ private Paint selectionPaint;
+
+ private boolean calibrated = false;
+ private AffineTransform transform = null;
+
+ private boolean showingTipCallouts = true;
+
+ private Map<Node, AffineTransform> tipLabelTransforms = new HashMap<Node, AffineTransform>();
+ private Map<Node, Shape> tipLabelBounds = new HashMap<Node, Shape>();
+ private Map<Node, Painter.Justification> tipLabelJustifications = new HashMap<Node, Painter.Justification>();
+
+ private Map<Node, AffineTransform> nodeLabelTransforms = new HashMap<Node, AffineTransform>();
+ private Map<Node, Shape> nodeLabelBounds = new HashMap<Node, Shape>();
+ private Map<Node, Painter.Justification> nodeLabelJustifications = new HashMap<Node, Painter.Justification>();
+
+ private Map<Node, AffineTransform> branchLabelTransforms = new HashMap<Node, AffineTransform>();
+ private Map<Node, Shape> branchLabelBounds = new HashMap<Node, Shape>();
+ private Map<Node, Painter.Justification> branchLabelJustifications = new HashMap<Node, Painter.Justification>();
+
+ private Map<Node, Shape> nodeBars = new HashMap<Node, Shape>();
+
+ private Map<Node, Shape> calloutPaths = new HashMap<Node, Shape>();
+
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreePaneListener.java b/src/jebl/gui/trees/treeviewer_dev/TreePaneListener.java
new file mode 100644
index 0000000..331602e
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreePaneListener.java
@@ -0,0 +1,10 @@
+package jebl.gui.trees.treeviewer_dev;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePaneListener.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public interface TreePaneListener {
+
+ void treePaneSettingsChanged();
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreePaneRuler.java b/src/jebl/gui/trees/treeviewer_dev/TreePaneRuler.java
new file mode 100644
index 0000000..166cbf6
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreePaneRuler.java
@@ -0,0 +1,56 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePaneRuler.java 294 2006-04-14 10:28:11Z rambaut $
+ */
+public class TreePaneRuler implements MouseListener, MouseMotionListener {
+ public TreePaneRuler(TreePane treePane) {
+ this.treePane = treePane;
+ treePane.addMouseListener(this);
+ treePane.addMouseMotionListener(this);
+ }
+
+ public void mouseClicked(MouseEvent mouseEvent) {
+// double selectedHeight = treePane.getHeightAt((Graphics2D)treePane.getGraphics(), mouseEvent.getPoint());
+// if (!mouseEvent.isShiftDown()) {
+// treePane.clearSelection();
+// }
+//
+// treePane.addSelectedHeight(isShiftDown);
+ }
+
+ public void mousePressed(MouseEvent mouseEvent) {
+ // This is used for dragging in combination with mouseDragged
+ // in the MouseMotionListener, below.
+ dragPoint = new Point2D.Double(mouseEvent.getPoint().getX(), mouseEvent.getPoint().getY());
+ }
+
+ public void mouseReleased(MouseEvent mouseEvent) {
+ }
+
+ public void mouseEntered(MouseEvent mouseEvent) {
+ treePane.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+ }
+
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ public void mouseMoved(MouseEvent mouseEvent) {
+ double height = treePane.getHeightAt((Graphics2D)treePane.getGraphics(), mouseEvent.getPoint());
+ treePane.setRuler(height);
+ }
+
+ public void mouseDragged(MouseEvent mouseEvent) {
+ }
+
+ private TreePane treePane;
+
+ private Point2D dragPoint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreePaneSelector.java b/src/jebl/gui/trees/treeviewer_dev/TreePaneSelector.java
new file mode 100644
index 0000000..b70a9f5
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreePaneSelector.java
@@ -0,0 +1,189 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.graphs.Node;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreePaneSelector.java 433 2006-08-27 19:34:13Z rambaut $
+ */
+public class TreePaneSelector implements MouseListener, MouseMotionListener {
+ public enum SelectionMode {
+ NODE,
+ CLADE,
+ TAXA
+ }
+
+ ;
+
+ public enum DragMode {
+ SELECT,
+ SCROLL
+ }
+
+ ;
+
+ public SelectionMode getSelectionMode() {
+ return selectionMode;
+ }
+
+ public DragMode getDragMode() {
+ return dragMode;
+ }
+
+ public void setSelectionMode(SelectionMode selectionMode) {
+ this.selectionMode = selectionMode;
+ }
+
+ public void setDragMode(DragMode dragMode) {
+ this.dragMode = dragMode;
+ }
+
+ public TreePaneSelector(TreePane treePane) {
+ this.treePane = treePane;
+ treePane.addMouseListener(this);
+ treePane.addMouseMotionListener(this);
+ }
+
+ public void mouseClicked(MouseEvent mouseEvent) {
+ Node selectedNode = treePane.getNodeAt((Graphics2D) treePane.getGraphics(), mouseEvent.getPoint());
+ if (!mouseEvent.isShiftDown()) {
+ treePane.clearSelection();
+ }
+
+ SelectionMode mode = selectionMode;
+ if (mouseEvent.isAltDown()) {
+ if (mode == SelectionMode.NODE) {
+ mode = SelectionMode.CLADE;
+ } else if (mode == SelectionMode.CLADE) {
+ mode = SelectionMode.NODE;
+ }
+ }
+
+ switch (mode) {
+ case NODE:
+ treePane.addSelectedNode(selectedNode);
+ break;
+ case CLADE:
+ treePane.addSelectedClade(selectedNode);
+ break;
+ case TAXA:
+ treePane.addSelectedTip(selectedNode);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown SelectionMode: " + selectionMode.name());
+ }
+ }
+
+ public void mousePressed(MouseEvent mouseEvent) {
+ // This is used for dragging in combination with mouseDragged
+ // in the MouseMotionListener, below.
+ dragPoint = new Point2D.Double(mouseEvent.getPoint().getX(), mouseEvent.getPoint().getY());
+ }
+
+ public void mouseReleased(MouseEvent mouseEvent) {
+ if (treePane.getDragRectangle() != null) {
+ Set<Node> selectedNodes = treePane.getNodesAt((Graphics2D) treePane.getGraphics(), treePane.getDragRectangle().getBounds());
+
+ if (!mouseEvent.isShiftDown()) {
+ treePane.clearSelection();
+ }
+
+ SelectionMode mode = selectionMode;
+ if (mouseEvent.isAltDown()) {
+ if (mode == SelectionMode.NODE) {
+ mode = SelectionMode.CLADE;
+ } else if (mode == SelectionMode.CLADE) {
+ mode = SelectionMode.NODE;
+ }
+ }
+
+ for (Node selectedNode : selectedNodes) {
+ switch (mode) {
+ case NODE:
+ treePane.addSelectedNode(selectedNode);
+ break;
+ case CLADE:
+ treePane.addSelectedClade(selectedNode);
+ break;
+ case TAXA:
+ treePane.addSelectedTip(selectedNode);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown SelectionMode: " + selectionMode.name());
+ }
+ }
+ }
+ treePane.setDragRectangle(null);
+ }
+
+ public void mouseEntered(MouseEvent mouseEvent) {
+ if (dragMode == DragMode.SCROLL || mouseEvent.isMetaDown()) {
+ treePane.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
+ } else {
+
+ treePane.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ public void mouseMoved(MouseEvent mouseEvent) {
+
+ }
+
+ public void mouseDragged(MouseEvent mouseEvent) {
+ //this situation can happen on MacOS, though very rare
+ if (dragPoint == null) {
+ return;
+ }
+ if (dragMode == DragMode.SCROLL || mouseEvent.isMetaDown()) {
+ // Calculate how far the mouse has been dragged from the point clicked in
+ // mousePressed, above.
+ int deltaX = (int) (mouseEvent.getX() - dragPoint.getX());
+ int deltaY = (int) (mouseEvent.getY() - dragPoint.getY());
+
+ // Get the currently visible window
+ Rectangle visRect = treePane.getVisibleRect();
+
+ // Calculate how much we need to scroll
+ if (deltaX > 0) {
+ deltaX = visRect.x - deltaX;
+ } else {
+ deltaX = visRect.x + visRect.width - deltaX;
+ }
+
+ if (deltaY > 0) {
+ deltaY = visRect.y - deltaY;
+ } else {
+ deltaY = visRect.y + visRect.height - deltaY;
+ }
+
+ // Scroll the visible region
+ Rectangle r = new Rectangle(deltaX, deltaY, 1, 1);
+ treePane.scrollRectToVisible(r);
+ } else {
+ double x1 = Math.min(dragPoint.getX(), mouseEvent.getPoint().getX());
+ double y1 = Math.min(dragPoint.getY(), mouseEvent.getPoint().getY());
+ double x2 = Math.max(dragPoint.getX(), mouseEvent.getPoint().getX());
+ double y2 = Math.max(dragPoint.getY(), mouseEvent.getPoint().getY());
+ treePane.setDragRectangle(new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1));
+ treePane.scrollPointToVisible(mouseEvent.getPoint());
+ }
+ }
+
+ private TreePane treePane;
+
+ private SelectionMode selectionMode = SelectionMode.NODE;
+
+ private DragMode dragMode = DragMode.SELECT;
+ private Point2D dragPoint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeSelectionListener.java b/src/jebl/gui/trees/treeviewer_dev/TreeSelectionListener.java
new file mode 100644
index 0000000..5fb2f59
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeSelectionListener.java
@@ -0,0 +1,10 @@
+package jebl.gui.trees.treeviewer_dev;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeSelectionListener.java 294 2006-04-14 10:28:11Z rambaut $
+ */
+public interface TreeSelectionListener {
+
+ void selectionChanged();
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeViewer.java b/src/jebl/gui/trees/treeviewer_dev/TreeViewer.java
new file mode 100644
index 0000000..2140834
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeViewer.java
@@ -0,0 +1,138 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.gui.trees.treeviewer_dev.treelayouts.TreeLayout;
+import jebl.gui.trees.treeviewer_dev.painters.BasicLabelPainter;
+import jebl.gui.trees.treeviewer_dev.painters.LabelPainter;
+import jebl.gui.trees.treeviewer_dev.painters.*;
+import jebl.gui.trees.treeviewer_dev.painters.ScaleBarPainter;
+import jebl.gui.trees.treeviewer_dev.decorators.AttributableDecorator;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.TransformedRootedTree;
+import jebl.evolution.trees.SortedRootedTree;
+
+import javax.swing.*;
+import java.awt.print.Printable;
+import java.awt.*;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeViewer.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public abstract class TreeViewer extends JPanel implements Printable {
+
+ public abstract void setTrees(Collection<? extends Tree> trees);
+
+ public abstract java.util.List<Tree> getTrees();
+
+ public abstract Tree getCurrentTree();
+
+ public abstract int getCurrentTreeIndex();
+
+ public abstract int getTreeCount();
+
+ public abstract void showTree(int index);
+
+ public abstract void setTreeLayout(TreeLayout treeLayout);
+
+ public abstract void setZoom(double zoom);
+
+ public abstract void setVerticalExpansion(double verticalExpansion);
+
+ public abstract boolean verticalExpansionAllowed();
+
+
+ public abstract boolean hasSelection();
+
+ public abstract void selectTaxa(SearchType searchType, String searchString, boolean caseSensitive);
+
+ public abstract void selectNodes(String attribute, SearchType searchType, String searchString, boolean caseSensitive);
+
+ public abstract void collapseSelectedNodes();
+
+ public abstract void annotateSelectedNodes(String name, Object value);
+
+ public abstract void annotateSelectedTips(String name, Object value);
+
+ public abstract void selectAll();
+
+ public abstract void clearSelectedTaxa();
+
+ public abstract void addTreeSelectionListener(TreeSelectionListener treeSelectionListener);
+
+ public abstract void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener);
+
+
+ public abstract void setSelectionMode(TreePaneSelector.SelectionMode selectionMode);
+
+ public abstract void setDragMode(TreePaneSelector.DragMode dragMode);
+
+ public abstract void setTipLabelPainter(LabelPainter<Node> tipLabelPainter);
+
+ public abstract void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter);
+
+ public abstract void setNodeBarPainter(NodeBarPainter nodeBarPainter);
+
+ //TEST
+ public abstract void setNodeHistPainter(NodeHistPainter nodeHistPainter);
+ //END TEST
+
+ public abstract void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter);
+
+ public abstract void setScaleBarPainter(ScaleBarPainter scaleBarPainter);
+
+ public abstract void setBranchDecorator(Decorator branchDecorator);
+
+ public abstract void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator);
+
+ public abstract void setSelectionPaint(Paint selectionPane);
+
+ public abstract Paint getSelectionPaint();
+
+ public abstract void setBranchStroke(BasicStroke branchStroke);
+
+
+ public abstract boolean isTransformBranchesOn();
+
+ public abstract TransformedRootedTree.Transform getBranchTransform();
+
+ public abstract void setTransformBranchesOn(boolean transformBranchesOn);
+
+ public abstract void setBranchTransform(TransformedRootedTree.Transform transform);
+
+
+ public abstract boolean isOrderBranchesOn();
+
+ public abstract SortedRootedTree.BranchOrdering getBranchOrdering();
+
+ public abstract void setOrderBranchesOn(boolean orderBranchesOn);
+
+ public abstract void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering);
+
+
+ public abstract JComponent getContentPane();
+
+ public abstract void addTreeViewerListener(TreeViewerListener listener);
+
+ public abstract void removeTreeViewerListener(TreeViewerListener listener);
+
+
+ public enum SearchType {
+ CONTAINS("Contains"),
+ STARTS_WITH("Starts with"),
+ ENDS_WITH("Ends with"),
+ MATCHES("Matches");
+
+ SearchType(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeViewerController.java b/src/jebl/gui/trees/treeviewer_dev/TreeViewerController.java
new file mode 100644
index 0000000..fded3be
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeViewerController.java
@@ -0,0 +1,348 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.gui.trees.treeviewer_dev.treelayouts.*;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeViewerController.java 655 2007-03-19 01:40:25Z richardmoir $
+ */
+public class TreeViewerController extends AbstractController {
+
+ public enum TreeLayoutType {
+ RECTILINEAR("Rectangle"),
+ POLAR("Polar"),
+ RADIAL("Radial");
+
+ TreeLayoutType(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+
+ private static final String CONTROLLER_TITLE = "Layout";
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(TreeViewerController.class);
+
+ private static final String CONTROLLER_KEY = "layout";
+
+ private static final String LAYOUT_KEY = "layoutType";
+ private static final String ZOOM_KEY = "zoom";
+ private static final String EXPANSION_KEY = "expansion";
+
+ // The defaults if there is nothing in the preferences
+ private static String DEFAULT_LAYOUT = TreeLayoutType.RECTILINEAR.name();
+
+ private final static int MAX_ZOOM_SLIDER = 10000;
+ private final static int DELTA_ZOOM_SLIDER = 200;
+
+ public TreeViewerController(final TreeViewer treeViewer) {
+
+ this.treeViewer = treeViewer;
+
+ final TreeLayoutType defaultLayout = TreeLayoutType.valueOf(PREFS.get(CONTROLLER_KEY + "." + LAYOUT_KEY, DEFAULT_LAYOUT));
+
+
+ titleLabel = new JLabel(CONTROLLER_TITLE);
+ optionsPanel = new OptionsPanel();
+
+ rectilinearTreeLayout = new RectilinearTreeLayout();
+ rectilinearTreeLayoutController = new RectilinearTreeLayoutController(rectilinearTreeLayout);
+
+ polarTreeLayout = new PolarTreeLayout();
+ polarTreeLayoutController = new PolarTreeLayoutController(polarTreeLayout);
+
+ radialTreeLayout = new RadialTreeLayout();
+ radialTreeLayoutController = new RadialTreeLayoutController(radialTreeLayout);
+
+ JPanel panel1 = new JPanel();
+ panel1.setOpaque(false);
+ panel1.setLayout(new BoxLayout(panel1, BoxLayout.LINE_AXIS));
+ Icon rectangularTreeIcon = IconUtils.getIcon(this.getClass(), "/jebl/gui/trees/treeviewer_dev/images/rectangularTree.png");
+ Icon polarTreeIcon = IconUtils.getIcon(this.getClass(), "/jebl/gui/trees/treeviewer_dev/images/polarTree.png");
+ Icon radialTreeIcon = IconUtils.getIcon(this.getClass(), "/jebl/gui/trees/treeviewer_dev/images/radialTree.png");
+ rectangularTreeToggle = new JToggleButton(rectangularTreeIcon);
+ polarTreeToggle = new JToggleButton(polarTreeIcon);
+ radialTreeToggle = new JToggleButton(radialTreeIcon);
+ rectangularTreeToggle.setToolTipText("Rectangular tree layout");
+ polarTreeToggle.setToolTipText("Polar tree layout");
+ radialTreeToggle.setToolTipText("Radial tree layout");
+ rectangularTreeToggle.putClientProperty("Quaqua.Button.style", "toggleWest");
+ polarTreeToggle.putClientProperty("Quaqua.Button.style", "toggleCenter");
+ radialTreeToggle.putClientProperty("Quaqua.Button.style", "toggleEast");
+ ButtonGroup buttonGroup = new ButtonGroup();
+ buttonGroup.add(rectangularTreeToggle);
+ buttonGroup.add(polarTreeToggle);
+ buttonGroup.add(radialTreeToggle);
+ rectangularTreeToggle.setSelected(true);
+ panel1.add(Box.createHorizontalGlue());
+ panel1.add(rectangularTreeToggle);
+ panel1.add(polarTreeToggle);
+ panel1.add(radialTreeToggle);
+ panel1.add(Box.createHorizontalGlue());
+
+ optionsPanel.addSpanningComponent(panel1);
+
+ zoomSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
+ zoomSlider.setOpaque(false);
+ zoomSlider.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+// zoomSlider.setPaintTicks(true);
+// zoomSlider.setPaintLabels(true);
+
+ zoomSlider.setValue(0);
+
+ zoomSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int value = zoomSlider.getValue();
+ treeViewer.setZoom(((double) value) / MAX_ZOOM_SLIDER);
+ }
+ });
+
+ optionsPanel.addComponentWithLabel("Zoom:", zoomSlider, true);
+
+ verticalExpansionSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
+ verticalExpansionSlider.setOpaque(false);
+// verticalExpansionSlider.setPaintTicks(true);
+// verticalExpansionSlider.setPaintLabels(true);
+
+ verticalExpansionSlider.setValue(0);
+
+ verticalExpansionSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int value = verticalExpansionSlider.getValue();
+ treeViewer.setVerticalExpansion(((double) value) / MAX_ZOOM_SLIDER);
+ }
+ });
+
+ verticalExpansionLabel = new JLabel("Expansion:");
+ optionsPanel.addComponents(verticalExpansionLabel, false, verticalExpansionSlider, true);
+
+ optionsPanel.addSeparator();
+
+ layoutPanel = new JPanel(new BorderLayout());
+ layoutPanel.setOpaque(false);
+ setTreeLayout(defaultLayout);
+ setExpansion();
+
+ optionsPanel.addSpanningComponent(layoutPanel);
+
+ rectangularTreeToggle.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (rectangularTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RECTILINEAR);
+ }
+ });
+ polarTreeToggle.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (polarTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.POLAR);
+ }
+ });
+ radialTreeToggle.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (radialTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RADIAL);
+ }
+ });
+
+ // Set some InputMaps and ActionMaps for key strokes. The ActionMaps are set in setExpansion()
+ // because they differ by whether vertical expansion is allowed for the current layout.
+ // The key strokes could be obtained from preferences and set in a preference dialog box
+ optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke("meta 0"), "resetZoom");
+ optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke("meta EQUALS"), "increasePrimaryZoom");
+ optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke("meta MINUS"), "decreasePrimaryZoom");
+ optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke("meta alt EQUALS"), "increaseSecondaryZoom");
+ optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke("meta alt MINUS"), "decreaseSecondaryZoom");
+
+ optionsPanel.getActionMap().put("resetZoom", resetZoomAction);
+
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return true;
+ }
+
+ public void setColouringAttributeName(String attributeName) {
+ rectilinearTreeLayout.setBranchColouringAttributeName(attributeName);
+ polarTreeLayout.setBranchColouringAttributeName(attributeName);
+ radialTreeLayout.setBranchColouringAttributeName(attributeName);
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ String treeLayoutName = (String)settings.get(CONTROLLER_KEY + "." + LAYOUT_KEY);
+ final TreeLayoutType layout = TreeLayoutType.valueOf(treeLayoutName);
+ switch (layout) {
+ case RECTILINEAR:
+ rectangularTreeToggle.setSelected(true);
+ break;
+ case POLAR:
+ polarTreeToggle.setSelected(true);
+ break;
+ case RADIAL:
+ radialTreeToggle.setSelected(true);
+ break;
+ }
+ zoomSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + ZOOM_KEY));
+ verticalExpansionSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + EXPANSION_KEY));
+
+ // These controllers are internal to TreeViewerController so settings must be done here
+ rectilinearTreeLayoutController.setSettings(settings);
+ polarTreeLayoutController.setSettings(settings);
+ radialTreeLayoutController.setSettings(settings);
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ if (rectangularTreeToggle.isSelected()) {
+ settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RECTILINEAR.name());
+ } else if (polarTreeToggle.isSelected()) {
+ settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.POLAR.name());
+ } else if (radialTreeToggle.isSelected()) {
+ settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RADIAL.name());
+ }
+ settings.put(CONTROLLER_KEY + "." + ZOOM_KEY, zoomSlider.getValue());
+ settings.put(CONTROLLER_KEY + "." + EXPANSION_KEY, verticalExpansionSlider.getValue());
+
+ // These controllers are internal to TreeViewerController so settings must be done here
+ rectilinearTreeLayoutController.getSettings(settings);
+ polarTreeLayoutController.getSettings(settings);
+ radialTreeLayoutController.getSettings(settings);
+ }
+
+ private void setTreeLayout(TreeLayoutType layoutType) {
+ switch (layoutType) {
+ case RECTILINEAR:
+ treeViewer.setTreeLayout(rectilinearTreeLayout);
+ setExpansion();
+ layoutPanel.removeAll();
+ layoutPanel.add(rectilinearTreeLayoutController.getPanel(), BorderLayout.CENTER);
+ fireControllerChanged();
+ break;
+ case POLAR:
+ treeViewer.setTreeLayout(polarTreeLayout);
+ setExpansion();
+ layoutPanel.removeAll();
+ layoutPanel.add(polarTreeLayoutController.getPanel(), BorderLayout.CENTER);
+ fireControllerChanged();
+ break;
+ case RADIAL:
+ treeViewer.setTreeLayout(radialTreeLayout);
+ setExpansion();
+ layoutPanel.removeAll();
+ layoutPanel.add(radialTreeLayoutController.getPanel(), BorderLayout.CENTER);
+ fireControllerChanged();
+ break;
+ default:
+ new RuntimeException("Unknown TreeLayoutType: " + layoutType);
+ }
+
+ }
+
+ private void setExpansion() {
+ if (treeViewer.verticalExpansionAllowed()) {
+ verticalExpansionLabel.setEnabled(true);
+ verticalExpansionSlider.setEnabled(true);
+ optionsPanel.getActionMap().put("increasePrimaryZoom", increaseVerticalExpansionAction);
+ optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseVerticalExpansionAction);
+ optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
+ optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
+ } else {
+ verticalExpansionLabel.setEnabled(false);
+ verticalExpansionSlider.setEnabled(false);
+ optionsPanel.getActionMap().put("increasePrimaryZoom", increaseZoomAction);
+ optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseZoomAction);
+ optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
+ optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
+ }
+ }
+
+ private Action resetZoomAction = new AbstractAction("Reset Zoom") {
+ public void actionPerformed(ActionEvent actionEvent) {
+ zoomSlider.setValue(0);
+ verticalExpansionSlider.setValue(0);
+ }
+ };
+
+ private Action increaseZoomAction = new AbstractAction("Zoom In") {
+ public void actionPerformed(ActionEvent actionEvent) {
+ zoomSlider.setValue(zoomSlider.getValue() + DELTA_ZOOM_SLIDER);
+ }
+ };
+
+ private Action decreaseZoomAction = new AbstractAction("Zoom In") {
+ public void actionPerformed(ActionEvent actionEvent) {
+ zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
+ }
+ };
+
+ private Action increaseVerticalExpansionAction = new AbstractAction("Expand Vertically") {
+ public void actionPerformed(ActionEvent actionEvent) {
+ verticalExpansionSlider.setValue(verticalExpansionSlider.getValue() + DELTA_ZOOM_SLIDER);
+ }
+ };
+
+ private Action decreaseVerticalExpansionAction = new AbstractAction("Unexpand Vertically") {
+ public void actionPerformed(ActionEvent actionEvent) {
+ int value = verticalExpansionSlider.getValue();
+ if (value > 0) {
+ verticalExpansionSlider.setValue(value - DELTA_ZOOM_SLIDER);
+ } else {
+ // If the vertical expansion was zero then assume the user is trying to un-zoom
+ zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
+ }
+ }
+ };
+
+
+ private JToggleButton rectangularTreeToggle;
+ private JToggleButton polarTreeToggle;
+ private JToggleButton radialTreeToggle;
+ private JSlider zoomSlider;
+ private JSlider verticalExpansionSlider;
+ private JLabel verticalExpansionLabel;
+
+ private final JPanel layoutPanel;
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final RectilinearTreeLayout rectilinearTreeLayout;
+ private final PolarTreeLayout polarTreeLayout;
+ private final RadialTreeLayout radialTreeLayout;
+
+ private final RectilinearTreeLayoutController rectilinearTreeLayoutController;
+ private final PolarTreeLayoutController polarTreeLayoutController;
+ private final RadialTreeLayoutController radialTreeLayoutController;
+
+ private final TreeViewer treeViewer;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeViewerListener.java b/src/jebl/gui/trees/treeviewer_dev/TreeViewerListener.java
new file mode 100644
index 0000000..43cd04b
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeViewerListener.java
@@ -0,0 +1,11 @@
+package jebl.gui.trees.treeviewer_dev;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeViewerListener.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public interface TreeViewerListener {
+ void treeChanged();
+
+ void treeSettingsChanged();
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreeViewerPanel.java b/src/jebl/gui/trees/treeviewer_dev/TreeViewerPanel.java
new file mode 100644
index 0000000..fa67c04
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreeViewerPanel.java
@@ -0,0 +1,145 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.gui.trees.treeviewer_dev.painters.*;
+import jebl.evolution.io.NewickImporter;
+import jebl.evolution.io.TreeImporter;
+import jebl.evolution.io.NexusImporter;
+import jebl.evolution.trees.Tree;
+import org.virion.jam.controlpalettes.BasicControlPalette;
+import org.virion.jam.controlpalettes.ControlPalette;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.io.BufferedReader;
+
+/**
+ * This is a panel that has a TreeViewer and a BasicControlPalette with
+ * the default Controllers and Painters.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: TreeViewerPanel.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class TreeViewerPanel extends JPanel {
+
+ public TreeViewerPanel(TreeViewer treeViewer, ControlPalette controlPalette) {
+
+ this.treeViewer = treeViewer;
+ this.controlPalette = controlPalette;
+
+ controlPalette.getPanel().setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY));
+ controlPalette.getPanel().setBackground(new Color(231, 237, 246));
+ controlPalette.getPanel().setOpaque(true);
+
+ controlPalette.addController(new TreeViewerController(treeViewer));
+
+ controlPalette.addController(new TreeAppearanceController(treeViewer));
+
+ controlPalette.addController(new TreesController(treeViewer));
+
+ // Create a tip label painter and its controller
+ BasicLabelPainter tipLabelPainter = new BasicLabelPainter(BasicLabelPainter.PainterIntent.TIP);
+ controlPalette.addController(new LabelPainterController("Tip Labels", "tipLabels", tipLabelPainter));
+ treeViewer.setTipLabelPainter(tipLabelPainter);
+
+ // Create a node label painter and its controller
+ BasicLabelPainter nodeLabelPainter = new BasicLabelPainter(BasicLabelPainter.PainterIntent.NODE);
+ nodeLabelPainter.setVisible(false);
+ controlPalette.addController(new LabelPainterController("Node Labels", "nodeLabels", nodeLabelPainter));
+ treeViewer.setNodeLabelPainter(nodeLabelPainter);
+
+ // Create a node shape painter and its controller
+ NodeBarPainter nodeBarPainter = new NodeBarPainter();
+ nodeBarPainter.setForeground(new Color(24, 32, 228, 128));
+ nodeBarPainter.setVisible(false);
+ controlPalette.addController(new NodeBarController("Node Bars", nodeBarPainter));
+ treeViewer.setNodeBarPainter(nodeBarPainter);
+
+ // Create a branch label painter and its controller
+ BasicLabelPainter branchLabelPainter = new BasicLabelPainter(BasicLabelPainter.PainterIntent.BRANCH);
+ branchLabelPainter.setVisible(false);
+ controlPalette.addController(new LabelPainterController("Branch Labels", "branchLabels", branchLabelPainter));
+ treeViewer.setBranchLabelPainter(branchLabelPainter);
+
+ // Create a scale bar painter and its controller
+ ScaleBarPainter scaleBarPainter = new ScaleBarPainter();
+ controlPalette.addController(new ScaleBarPainterController(scaleBarPainter));
+ treeViewer.setScaleBarPainter(scaleBarPainter);
+
+ /*
+ * testing
+ */
+ // Create a node shape painter and its controller
+ NodeHistPainter nodeHistPainter = new NodeHistPainter();
+ nodeHistPainter.setForeground(new Color(24, 32, 228, 128));
+ nodeHistPainter.setVisible(false);
+ controlPalette.addController(new NodeHistController("Node Hists", nodeHistPainter));
+ treeViewer.setNodeHistPainter(nodeHistPainter);
+
+ /*
+ * end testing
+ */
+ setLayout(new BorderLayout());
+
+ add(treeViewer, BorderLayout.CENTER);
+ add(controlPalette.getPanel(), BorderLayout.WEST);
+
+ }
+
+ public TreeViewer getTreeViewer() {
+ return treeViewer;
+ }
+
+ public ControlPalette getControlPalette() {
+ return controlPalette;
+ }
+
+ private final TreeViewer treeViewer;
+ private final ControlPalette controlPalette;
+
+ static public void main(String[] args) {
+
+ JFrame frame = new JFrame("TreeViewer Test");
+
+ TreeViewer treeViewer = new DefaultTreeViewer();
+ ControlPalette controlPalette = new BasicControlPalette(200, BasicControlPalette.DisplayMode.ONLY_ONE_OPEN);
+
+ frame.getContentPane().add(new TreeViewerPanel(treeViewer, controlPalette), BorderLayout.CENTER);
+
+ try {
+ File inputFile = null;
+
+ if (args.length > 0) {
+ inputFile = new File(args[0]);
+ }
+
+ if (inputFile == null) {
+ // No input file name was given so throw up a dialog box...
+ java.awt.FileDialog chooser = new java.awt.FileDialog(frame, "Select NEXUS Tree File",
+ java.awt.FileDialog.LOAD);
+ chooser.setVisible(true);
+ inputFile = new java.io.File(chooser.getDirectory(), chooser.getFile());
+ chooser.dispose();
+ }
+
+ if (inputFile == null) {
+ throw new RuntimeException("No file specified");
+ }
+
+// TreeImporter importer = new NewickImporter(new FileReader(inputFile));
+ Reader reader = new BufferedReader(new FileReader(inputFile));
+ TreeImporter importer = new NewickImporter(reader, false);
+ java.util.List<Tree> trees = importer.importTrees();
+ reader.close();
+ treeViewer.setTrees(trees);
+ } catch (Exception ie) {
+ ie.printStackTrace();
+ System.exit(1);
+ }
+
+ frame.setSize(640, 480);
+ frame.setVisible(true);
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/TreesController.java b/src/jebl/gui/trees/treeviewer_dev/TreesController.java
new file mode 100644
index 0000000..dc2c02a
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/TreesController.java
@@ -0,0 +1,157 @@
+package jebl.gui.trees.treeviewer_dev;
+
+import jebl.evolution.trees.SortedRootedTree;
+import jebl.evolution.trees.TransformedRootedTree;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreesController.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class TreesController extends AbstractController {
+
+ private static final String CONTROLLER_TITLE = "Trees";
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(TreesController.class);
+
+ private static final String CONTROLLER_KEY = "trees";
+
+ private static final String TRANSFORM_KEY = "transform";
+ private static final String TRANSFORM_TYPE_KEY = "transformType";
+ private static final String ORDER_KEY = "order";
+ private static final String ORDER_TYPE_KEY = "orderType";
+
+
+ public TreesController(final TreeViewer treeViewer) {
+ this.treeViewer = treeViewer;
+
+ titleLabel = new JLabel(CONTROLLER_TITLE);
+
+ optionsPanel = new OptionsPanel();
+
+ transformCheck = new JCheckBox("Transform branches");
+ transformCheck.setOpaque(false);
+ optionsPanel.addComponent(transformCheck);
+
+ transformCheck.setSelected(treeViewer.isTransformBranchesOn());
+
+ transformCombo = new JComboBox(TransformedRootedTree.Transform.values());
+ transformCombo.setOpaque(false);
+ transformCombo.setSelectedItem(treeViewer.getBranchTransform());
+ transformCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ treeViewer.setBranchTransform(
+ (TransformedRootedTree.Transform) transformCombo.getSelectedItem());
+
+ }
+ });
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Transform:", transformCombo);
+ label1.setEnabled(transformCheck.isSelected());
+ transformCombo.setEnabled(transformCheck.isSelected());
+
+ transformCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean selected = transformCheck.isSelected();
+ label1.setEnabled(selected);
+ transformCombo.setEnabled(selected);
+
+ treeViewer.setTransformBranchesOn(selected);
+ }
+ });
+
+ orderCheck = new JCheckBox("Order branches");
+ orderCheck.setOpaque(false);
+ optionsPanel.addComponent(orderCheck);
+
+ orderCheck.setSelected(treeViewer.isOrderBranchesOn());
+
+ orderCombo = new JComboBox(SortedRootedTree.BranchOrdering.values());
+ orderCombo.setOpaque(false);
+ orderCombo.setSelectedItem(treeViewer.getBranchOrdering());
+ orderCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ treeViewer.setBranchOrdering(
+ (SortedRootedTree.BranchOrdering) orderCombo.getSelectedItem());
+ }
+ });
+
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Ordering:", orderCombo);
+ label2.setEnabled(orderCheck.isSelected());
+ orderCombo.setEnabled(orderCheck.isSelected());
+
+ orderCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ label2.setEnabled(orderCheck.isSelected());
+ orderCombo.setEnabled(orderCheck.isSelected());
+
+ treeViewer.setOrderBranchesOn(orderCheck.isSelected());
+ }
+ });
+
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ treeViewer.setTransformBranchesOn(transformCheck.isSelected());
+ treeViewer.setBranchTransform((TransformedRootedTree.Transform) transformCombo.getSelectedItem());
+ treeViewer.setOrderBranchesOn(orderCheck.isSelected());
+ treeViewer.setBranchOrdering((SortedRootedTree.BranchOrdering) orderCombo.getSelectedItem());
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ transformCheck.setSelected((Boolean) settings.get(CONTROLLER_KEY + "." + TRANSFORM_KEY));
+ String transformName = (String)settings.get(CONTROLLER_KEY + "." + TRANSFORM_TYPE_KEY);
+ for (TransformedRootedTree.Transform transform : TransformedRootedTree.Transform.values()) {
+ if (transform.toString().equalsIgnoreCase(transformName)) {
+ transformCombo.setSelectedItem(transform);
+ }
+ }
+
+ orderCheck.setSelected((Boolean) settings.get(CONTROLLER_KEY + "." + ORDER_KEY));
+ String orderName = (String)settings.get(CONTROLLER_KEY + "." + ORDER_TYPE_KEY);
+ for (SortedRootedTree.BranchOrdering order : SortedRootedTree.BranchOrdering.values()) {
+ if (order.toString().equalsIgnoreCase(orderName)) {
+ orderCombo.setSelectedItem(order);
+ }
+ }
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(CONTROLLER_KEY + "." + TRANSFORM_KEY, transformCheck.isSelected());
+ settings.put(CONTROLLER_KEY + "." + TRANSFORM_TYPE_KEY, transformCombo.getSelectedItem().toString());
+ settings.put(CONTROLLER_KEY + "." + ORDER_KEY, orderCheck.isSelected());
+ settings.put(CONTROLLER_KEY + "." + ORDER_TYPE_KEY, orderCombo.getSelectedItem().toString());
+ }
+
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final JCheckBox transformCheck;
+ private final JComboBox transformCombo;
+
+ private final JCheckBox orderCheck;
+ private final JComboBox orderCombo;
+
+ private final TreeViewer treeViewer;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/decorators/AttributableDecorator.java b/src/jebl/gui/trees/treeviewer_dev/decorators/AttributableDecorator.java
new file mode 100644
index 0000000..661e2a7
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/decorators/AttributableDecorator.java
@@ -0,0 +1,120 @@
+package jebl.gui.trees.treeviewer_dev.decorators;
+
+import jebl.evolution.taxa.Taxon;
+import jebl.util.Attributable;
+
+import java.awt.*;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: AttributableDecorator.java 433 2006-08-27 19:34:13Z rambaut $
+ */
+public class AttributableDecorator implements Decorator {
+
+ // Decorator INTERFACE
+ public Paint getPaint(Paint paint) {
+ if (this.paint == null) return paint;
+ return this.paint;
+ }
+
+ public Paint getFillPaint(Paint paint) {
+ if (this.fillPaint == null) return paint;
+ return this.fillPaint;
+ }
+
+ public Stroke getStroke(Stroke stroke) {
+ if (this.stroke == null) return stroke;
+ return this.stroke;
+ }
+
+ public Font getFont(Font font) {
+ if (this.font == null) return font;
+ return this.font;
+ }
+
+ public void setItem(Object item) {
+ if (item instanceof Attributable) {
+ setAttributableItem((Attributable)item);
+ }
+ }
+
+ // Public methods
+ public String getFontAttributeName() {
+ return fontAttributeName;
+ }
+
+ public void setFontAttributeName(String fontAttributeName) {
+ this.fontAttributeName = fontAttributeName;
+ }
+
+ public String getPaintAttributeName() {
+ return paintAttributeName;
+ }
+
+ public void setPaintAttributeName(String paintAttributeName) {
+ this.paintAttributeName = paintAttributeName;
+ }
+
+ public String getStrokeAttributeName() {
+ return strokeAttributeName;
+ }
+
+ public void setStrokeAttributeName(String strokeAttributeName) {
+ this.strokeAttributeName = strokeAttributeName;
+ }
+
+ // Private methods
+ private void setAttributableItem(Attributable item) {
+ if (paintAttributeName != null) {
+ Color color = getColorAttribute(item.getAttribute(paintAttributeName));
+ if (color != null) {
+ paint = color;
+ fillPaint = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 2);
+ } else {
+ paint = null;
+ fillPaint = null;
+ }
+ }
+ if (fontAttributeName != null) {
+ font = getFontAttribute(item.getAttribute(fontAttributeName));
+ }
+ if (strokeAttributeName != null) {
+ stroke = getStrokeAttribute(item.getAttribute(strokeAttributeName));
+ }
+ }
+
+ private Color getColorAttribute(Object value) {
+ if (value != null) {
+ if (value instanceof Color) {
+ return (Color)value;
+ }
+ try {
+ return Color.decode(value.toString());
+ } catch (NumberFormatException nfe) {
+ //
+ }
+ }
+ return null;
+ }
+
+ private Font getFontAttribute(Object value) {
+ if (value != null) {
+ return Font.decode(value.toString());
+ }
+ return null;
+ }
+
+ private Stroke getStrokeAttribute(Object value) {
+ return null;
+ }
+
+ private String paintAttributeName = null;
+ private String fontAttributeName = null;
+ private String strokeAttributeName = null;
+
+ private Paint paint = null;
+ private Paint fillPaint = null;
+ private Font font = null;
+ private Stroke stroke = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/decorators/ContinuousColorDecorator.java b/src/jebl/gui/trees/treeviewer_dev/decorators/ContinuousColorDecorator.java
new file mode 100644
index 0000000..c53b4a0
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/decorators/ContinuousColorDecorator.java
@@ -0,0 +1,144 @@
+package jebl.gui.trees.treeviewer_dev.decorators;
+
+import jebl.util.Attributable;
+
+import java.util.*;
+import java.awt.*;
+
+/**
+ * This decorator takes an attribute name and a set of attibutable Objects. It
+ * autodetects the type of attributes and then provides a colouring scheme for them
+ * based on a gradient between color1 & color2.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: ContinuousColorDecorator.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class ContinuousColorDecorator implements Decorator {
+
+ enum SchemeType {
+ DISCRETE,
+ CONTINUOUS
+ }
+
+ public ContinuousColorDecorator(String attributeName,
+ Set<? extends Attributable> items,
+ Color color1, Color color2) throws NumberFormatException {
+ this.attributeName = attributeName;
+ this.color1 = new float[4];
+ color1.getRGBComponents(this.color1);
+ this.color2 = new float[4];
+ color2.getRGBComponents(this.color2);
+
+ // First collect the set of all attribute values
+ Set<Object> values = new TreeSet<Object>();
+ for (Attributable item : items) {
+ Object value = item.getAttribute(attributeName);
+ if (value != null) {
+ values.add(value);
+ }
+ }
+
+ boolean isNumber = true;
+
+ // Find the range of numbers
+ for (Object value : values) {
+ double realValue = -1.0;
+
+ if (value instanceof Boolean) {
+ realValue = ((Boolean)value ? 1 : 0);
+ } else if (value instanceof Number) {
+ realValue = ((Number)value).doubleValue();
+ } else if (value instanceof String) {
+ // it is a string but it could still code for
+ // a boolean, integer or real
+ if (value.toString().equalsIgnoreCase("true")) {
+ realValue = 1;
+ } else if (value.toString().equalsIgnoreCase("false")) {
+ realValue = 0;
+ } else {
+ try {
+ realValue = Double.parseDouble(value.toString());
+ } catch(NumberFormatException nfe) {
+ isNumber = false;
+ }
+ }
+ }
+
+ if (isNumber) {
+ if (realValue < minValue) {
+ minValue = realValue;
+ }
+ if (realValue > maxValue) {
+ maxValue = realValue;
+ }
+
+ }
+ }
+
+ if (!isNumber) {
+ throw new NumberFormatException("One or more values for this attribute are not numbers");
+ }
+
+ }
+
+ // Decorator INTERFACE
+ public Paint getPaint(Paint paint) {
+ if (this.paint == null) return paint;
+ return this.paint;
+ }
+
+ public Paint getFillPaint(Paint paint) {
+ if (this.fillPaint == null) return paint;
+ return fillPaint;
+ }
+
+ public Stroke getStroke(Stroke stroke) {
+ return stroke;
+ }
+
+ public Font getFont(Font font) {
+ return font;
+ }
+
+ public void setItem(Object item) {
+ if (item instanceof Attributable) {
+ setAttributableItem((Attributable)item);
+ }
+ }
+
+ // Private methods
+ private void setAttributableItem(Attributable item) {
+ paint = null;
+ Object value = item.getAttribute(attributeName);
+
+ if (value != null) {
+
+ double number = 0.0;
+ if (value instanceof Number) {
+ number = ((Number)value).doubleValue();
+ } else {
+ number = Double.parseDouble(value.toString());
+ }
+ float p = (float)((number - minValue)/(maxValue - minValue));
+ float q = 1.0F - p;
+
+ paint = new Color(
+ color1[0] * p + color2[0] * q,
+ color1[1] * p + color2[1] * q,
+ color1[2] * p + color2[2] * q,
+ color1[3] * p + color2[3] * q);
+ fillPaint = new Color(paint.getRed(), paint.getGreen(), paint.getBlue(), paint.getAlpha() / 2);
+ }
+ }
+
+ private final String attributeName;
+
+ private final float[] color1;
+ private final float[] color2;
+
+ private double minValue = Double.MAX_VALUE;
+ private double maxValue = Double.MIN_VALUE;
+
+ private Color paint = null;
+ private Color fillPaint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/decorators/Decorator.java b/src/jebl/gui/trees/treeviewer_dev/decorators/Decorator.java
new file mode 100644
index 0000000..b4ea4ef
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/decorators/Decorator.java
@@ -0,0 +1,19 @@
+package jebl.gui.trees.treeviewer_dev.decorators;
+
+import jebl.evolution.taxa.Taxon;
+
+import java.awt.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: Decorator.java 433 2006-08-27 19:34:13Z rambaut $
+ */
+public interface Decorator {
+
+ void setItem(Object item);
+
+ Paint getPaint(Paint paint);
+ Paint getFillPaint(Paint paint);
+ Stroke getStroke(Stroke stroke);
+ Font getFont(Font font);
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/decorators/DiscreteColorDecorator.java b/src/jebl/gui/trees/treeviewer_dev/decorators/DiscreteColorDecorator.java
new file mode 100644
index 0000000..2a38e5a
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/decorators/DiscreteColorDecorator.java
@@ -0,0 +1,165 @@
+package jebl.gui.trees.treeviewer_dev.decorators;
+
+import jebl.util.Attributable;
+
+import java.awt.*;
+import java.util.*;
+
+/**
+ * This decorator takes an attribute name and a set of attibutable Objects.
+ * Colours are given to each individual value.
+ *
+ * If the data take more values than colors, then they will wrap around
+ *
+ * @author Andrew Rambaut
+ * @version $Id: DiscreteColorDecorator.java 639 2007-02-15 10:05:28Z rambaut $
+ */
+public class DiscreteColorDecorator implements Decorator {
+
+
+ public static Color[] DEFAULT_PAINTS = new Color[] {
+ new Color(64,35,225),
+ new Color(229,35,60),
+ new Color(255,174,34),
+ new Color(86,255,34),
+ new Color(35,141,148),
+ new Color(146,35,142),
+ new Color(255,90,34),
+ new Color(239,255,34),
+ Color.DARK_GRAY
+ };
+
+ public DiscreteColorDecorator() {
+ this(DEFAULT_PAINTS);
+ }
+
+ public DiscreteColorDecorator(Color[] paints) {
+ this.paints = paints;
+ }
+
+ public DiscreteColorDecorator(String attributeName, Set<? extends Attributable> items) {
+ this(attributeName, items, DEFAULT_PAINTS);
+ }
+
+ public DiscreteColorDecorator(String attributeName, Set<? extends Attributable> items, Color[] paints) {
+ this.attributeName = attributeName;
+
+ // First collect the set of all attribute values
+ Set<Object> sortedValues = new TreeSet<Object>();
+ Set<Object> unsortedValues = new HashSet<Object>();
+
+ for (Attributable item : items) {
+ Object value = item.getAttribute(attributeName);
+ if (value != null) {
+ if (value instanceof Comparable) {
+ sortedValues.add(value);
+ } else {
+ unsortedValues.add(value);
+ }
+ }
+ }
+
+ if (unsortedValues.size() > 0) {
+ unsortedValues.addAll(sortedValues);
+ setValues(unsortedValues, paints);
+ } else {
+ setValues(sortedValues, paints);
+ }
+
+ }
+
+ public void setValues(Collection<? extends Object> values, Color[] paints) {
+ colourMap = new HashMap<Object, Paint>();
+ this.paints = paints;
+
+ // now create a paint map for these values
+ int i = 0;
+ for (Object value : values) {
+ colourMap.put(value, paints[i]);
+ i = (i + 1) % paints.length;
+ }
+
+ }
+
+ // Decorator INTERFACE
+ public Paint getPaint(Paint paint) {
+ if (this.paint == null) return paint;
+ return this.paint;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ public Paint getFillPaint(Paint paint) {
+ return paint;
+ }
+
+ public Stroke getStroke(Stroke stroke) {
+ return stroke;
+ }
+
+ public Font getFont(Font font) {
+ return font;
+ }
+
+ public void setItem(Object item) {
+ if (item instanceof Attributable) {
+ setAttributableItem((Attributable)item);
+ } else {
+ setValue(item);
+ }
+ }
+
+ public static boolean isDiscrete(String attributeName, Set<? extends Attributable> items) {
+ // First collect the set of all attribute values
+ Set<Object> values = new HashSet<Object>();
+ for (Attributable item : items) {
+ Object value = item.getAttribute(attributeName);
+ if (value != null) {
+ values.add(value);
+ }
+ }
+
+ boolean isNumber = true;
+ boolean isInteger = true;
+
+ for (Object value : values) {
+ if (value instanceof Number) {
+ if (((Number)value).doubleValue() != ((Number)value).intValue()) {
+ isInteger = false;
+ }
+ } else {
+ isNumber = false;
+ }
+ }
+
+ if (isNumber && !isInteger) return false;
+
+ return true;
+ }
+
+ // Private methods
+ private void setAttributableItem(Attributable item) {
+ paint = null;
+ Object value = item.getAttribute(attributeName);
+ if (value != null) {
+ setValue(value);
+ }
+ }
+
+ private void setValue(Object value) {
+ if (colourMap != null) {
+ paint = colourMap.get(value);
+ } else if (value instanceof Number) {
+ int index = ((Number)value).intValue() % paints.length;
+ paint = paints[index];
+ }
+ }
+
+ private String attributeName = null;
+
+ private Map<Object, Paint> colourMap = null;
+ private Paint[] paints = null;
+ private Paint paint = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/images/polarTree.png b/src/jebl/gui/trees/treeviewer_dev/images/polarTree.png
new file mode 100644
index 0000000..c127e60
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer_dev/images/polarTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer_dev/images/radialTree.png b/src/jebl/gui/trees/treeviewer_dev/images/radialTree.png
new file mode 100644
index 0000000..758e14c
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer_dev/images/radialTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer_dev/images/rectangularTree.png b/src/jebl/gui/trees/treeviewer_dev/images/rectangularTree.png
new file mode 100644
index 0000000..c1ac499
Binary files /dev/null and b/src/jebl/gui/trees/treeviewer_dev/images/rectangularTree.png differ
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/AbstractPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/AbstractPainter.java
new file mode 100644
index 0000000..5c8adfd
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/AbstractPainter.java
@@ -0,0 +1,32 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: AbstractPainter.java 373 2006-07-01 15:18:27Z rambaut $
+ */
+public abstract class AbstractPainter<T> implements Painter<T> {
+ public void addPainterListener(PainterListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removePainterListener(PainterListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void firePainterChanged() {
+ for (PainterListener listener : listeners) {
+ listener.painterChanged();
+ }
+ }
+
+ public void firePainterSettingsChanged() {
+ for (PainterListener listener : listeners) {
+ listener.painterSettingsChanged();
+ }
+ }
+
+ private final List<PainterListener> listeners = new ArrayList<PainterListener>();
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/BasicLabelPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/BasicLabelPainter.java
new file mode 100644
index 0000000..8f26e08
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/BasicLabelPainter.java
@@ -0,0 +1,306 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.evolution.taxa.Taxon;
+import jebl.gui.trees.treeviewer_dev.TreePane;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+import java.util.List;
+
+/**
+ * A simple implementation of LabelPainter that can be used to display
+ * tip, node or branch labels. It can display, taxon names, branch lengths,
+ * node heights or other attributeNames of nodes.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: BasicLabelPainter.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class BasicLabelPainter extends LabelPainter<Node> {
+
+ public static final String TAXON_NAMES = "Taxon Names";
+ public static final String NODE_HEIGHTS = "Node Heights";
+ public static final String BRANCH_LENGTHS = "Branch Lengths";
+
+ public enum PainterIntent {
+ NODE,
+ BRANCH,
+ TIP
+ };
+
+ public BasicLabelPainter(PainterIntent intent) {
+ this.intent = intent;
+
+ setupAttributes(null);
+
+ if (this.displayAttribute == null) {
+ this.displayAttribute = attributes[0];
+ } else {
+ this.displayAttribute = "";
+ }
+
+ }
+
+ public void setupAttributes(Collection<? extends Tree> trees) {
+
+ List<String> attributeNames = new ArrayList<String>();
+ switch( intent ) {
+ case TIP: {
+ attributeNames.add(TAXON_NAMES);
+ attributeNames.add(NODE_HEIGHTS);
+ attributeNames.add(BRANCH_LENGTHS);
+ break;
+ }
+ case NODE: {
+ attributeNames.add(NODE_HEIGHTS);
+ attributeNames.add(BRANCH_LENGTHS);
+ break;
+ }
+ case BRANCH: {
+ attributeNames.add(BRANCH_LENGTHS);
+ attributeNames.add(NODE_HEIGHTS);
+ break;
+ }
+ }
+
+ if (trees != null) {
+ for (Tree tree : trees) {
+ Set<String> nodeAttributes = new TreeSet<String>();
+ if (intent == PainterIntent.TIP) {
+ for (Node node : tree.getExternalNodes()) {
+ nodeAttributes.addAll(node.getAttributeNames());
+ }
+ } else if (intent == PainterIntent.NODE) {
+ for (Node node : tree.getInternalNodes()) {
+ nodeAttributes.addAll(node.getAttributeNames());
+ }
+ } else {
+ for (Node node : tree.getNodes()) {
+ nodeAttributes.addAll(node.getAttributeNames());
+ }
+ }
+ for (String attributeName : nodeAttributes) {
+ if (!attributeName.startsWith("!")) {
+ attributeNames.add(attributeName);
+ }
+ }
+ }
+ }
+
+ this.attributes = new String[attributeNames.size()];
+ attributeNames.toArray(this.attributes);
+
+ firePainterSettingsChanged();
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Decorator getBorderDecorator() {
+ return borderDecorator;
+ }
+
+ public void setBorderDecorator(Decorator borderDecorator) {
+ this.borderDecorator = borderDecorator;
+ }
+
+ public Decorator getTextDecorator() {
+ return textDecorator;
+ }
+
+ public void setTextDecorator(Decorator textDecorator) {
+ this.textDecorator = textDecorator;
+ }
+
+ public Tree getTree() {
+ return treePane.getTree();
+ }
+
+ protected String getLabel(Tree tree, Node node) {
+ if (displayAttribute.equalsIgnoreCase(TAXON_NAMES)) {
+ Taxon taxon = tree.getTaxon(node);
+ if (taxon != null) {
+ if (textDecorator != null) {
+ textDecorator.setItem(taxon);
+ }
+ return taxon.getName();
+ } else {
+ return "unlabelled";
+ }
+ }
+
+ if ( tree instanceof RootedTree) {
+ final RootedTree rtree = (RootedTree) tree;
+
+ if (textDecorator != null) {
+ textDecorator.setItem(node);
+ }
+
+ if (displayAttribute.equalsIgnoreCase(NODE_HEIGHTS) ) {
+ return getNumberFormat().format(rtree.getHeight(node));
+ } else if (displayAttribute.equalsIgnoreCase(BRANCH_LENGTHS) ) {
+ return getNumberFormat().format(rtree.getLength(node));
+ }
+ }
+
+ return formatValue(node.getAttribute(displayAttribute));
+ }
+
+ private String formatValue(Object value) {
+ if (value != null) {
+ if (value instanceof Double) {
+ return getNumberFormat().format(value);
+ } else if (value instanceof Object[]) {
+ Object[] values = (Object[])value;
+
+ if (values.length == 0) return null;
+ if (values.length == 1) return formatValue(values[0]);
+
+ StringBuilder builder = new StringBuilder("[");
+ builder.append(formatValue(values[0]));
+ for (int i = 1; i < values.length; i++) {
+ builder.append(",");
+ builder.append(formatValue(values[i]));
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+ return value.toString();
+ }
+ return null;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, Node item) {
+ Tree tree = treePane.getTree();
+
+ String label = getLabel(tree, item);
+
+ final Font oldFont = g2.getFont();
+ if (textDecorator != null) {
+ g2.setFont(textDecorator.getFont(getFont()));
+ } else {
+ g2.setFont(getFont());
+ }
+
+ FontMetrics fm = g2.getFontMetrics();
+ preferredHeight = fm.getHeight();
+ preferredWidth = 0;
+
+ if (label != null) {
+ Rectangle2D rect = fm.getStringBounds(label, g2);
+ preferredWidth = rect.getWidth();
+ }
+
+ yOffset = (float)fm.getAscent();
+
+ g2.setFont(oldFont);
+
+ return new Rectangle2D.Double(0.0, 0.0, preferredWidth, preferredHeight);
+ }
+
+ public double getPreferredWidth() {
+ return preferredWidth;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight + yOffset;
+ }
+
+ public void paint(Graphics2D g2, Node item, Justification justification, Rectangle2D bounds) {
+ Tree tree = treePane.getTree();
+
+ String label = getLabel(tree, item);
+
+ Font oldFont = g2.getFont();
+
+ Paint backgroundPaint = getBackground();
+ Paint borderPaint = getBorderPaint();
+ Stroke borderStroke = getBorderStroke();
+
+ if (borderDecorator != null) {
+ backgroundPaint = borderDecorator.getPaint(backgroundPaint);
+ borderPaint = borderDecorator.getPaint(borderPaint);
+ borderStroke = borderDecorator.getStroke(borderStroke);
+ }
+
+ if (backgroundPaint != null) {
+ g2.setPaint(backgroundPaint);
+ g2.fill(bounds);
+ }
+
+ if (borderPaint != null && borderStroke != null) {
+ g2.setPaint(borderPaint);
+ g2.setStroke(borderStroke);
+ g2.draw(bounds);
+ }
+
+ if (textDecorator != null) {
+ g2.setPaint(textDecorator.getPaint(getForeground()));
+ g2.setFont(textDecorator.getFont(getFont()));
+ } else {
+ g2.setPaint(getForeground());
+ g2.setFont(getFont());
+ }
+
+ if (label != null) {
+
+ Rectangle2D rect = g2.getFontMetrics().getStringBounds(label, g2);
+
+ float xOffset;
+ float y = yOffset + (float) bounds.getY();
+ switch (justification) {
+ case CENTER:
+ xOffset = (float)(-rect.getWidth()/2.0);
+ y = yOffset + (float) rect.getY();
+//xOffset = (float) (bounds.getX() + (bounds.getWidth() - rect.getWidth()) / 2.0);
+ break;
+ case FLUSH:
+ case LEFT:
+ xOffset = (float) bounds.getX();
+ break;
+ case RIGHT:
+ xOffset = (float) (bounds.getX() + bounds.getWidth() - rect.getWidth());
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alignment enum option");
+ }
+
+ g2.drawString(label, xOffset, y);
+ }
+
+ g2.setFont(oldFont);
+ }
+
+ public String[] getAttributes() {
+ return attributes;
+ }
+
+ public void setDisplayAttribute(String displayAttribute) {
+ this.displayAttribute = displayAttribute;
+ firePainterChanged();
+ }
+
+ private PainterIntent intent;
+
+ private double preferredWidth;
+ private double preferredHeight;
+ private float yOffset;
+
+ protected String displayAttribute;
+ protected String[] attributes;
+
+ protected TreePane treePane;
+
+ private Decorator textDecorator = null;
+ private Decorator borderDecorator = null;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainter.java
new file mode 100644
index 0000000..05e3baf
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainter.java
@@ -0,0 +1,102 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.trees.Tree;
+import jebl.gui.trees.treeviewer_dev.decorators.Decorator;
+
+import java.awt.*;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.prefs.Preferences;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: LabelPainter.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public abstract class LabelPainter<T> extends AbstractPainter<T> {
+
+ protected LabelPainter() {
+ }
+
+ // Abstract
+
+ public abstract String[] getAttributes();
+
+ public abstract void setupAttributes(Collection<? extends Tree> trees);
+
+ public abstract void setDisplayAttribute(String displayAttribute);
+
+ // Getters
+
+ public Paint getForeground() {
+ return foreground;
+ }
+
+ public Paint getBackground() {
+ return background;
+ }
+
+ public Paint getBorderPaint() {
+ return borderPaint;
+ }
+
+ public Stroke getBorderStroke() {
+ return borderStroke;
+ }
+
+ public Font getFont() {
+ return font;
+ }
+
+ public NumberFormat getNumberFormat() {
+ return numberFormat;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ // Setters
+
+ public void setBackground(Paint background) {
+ this.background = background;
+ firePainterChanged();
+ }
+
+ public void setBorder(Paint borderPaint, Stroke borderStroke) {
+ this.borderPaint = borderPaint;
+ this.borderStroke = borderStroke;
+ firePainterChanged();
+ }
+
+ public void setFont(Font font) {
+ this.font = font;
+ firePainterChanged();
+ }
+
+ public void setForeground(Paint foreground) {
+ this.foreground = foreground;
+ firePainterChanged();
+ }
+
+ public void setNumberFormat(NumberFormat numberFormat) {
+ this.numberFormat = numberFormat;
+ firePainterChanged();
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ firePainterChanged();
+ }
+
+ private Paint foreground = Color.BLACK;
+ private Paint background = null;
+ private Paint borderPaint = null;
+ private Stroke borderStroke = null;
+
+ private Font font;
+ private boolean visible = true;
+
+ private NumberFormat numberFormat = null;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainterController.java b/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainterController.java
new file mode 100644
index 0000000..fc4e641
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/LabelPainterController.java
@@ -0,0 +1,213 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+import jebl.evolution.trees.TransformedRootedTree;
+import jebl.evolution.trees.SortedRootedTree;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: LabelPainterController.java 642 2007-02-16 19:35:15Z rambaut $
+ */
+public class LabelPainterController extends AbstractController {
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(LabelPainterController.class);
+
+ private static final String FONT_NAME_KEY = "fontName";
+ private static final String FONT_SIZE_KEY = "fontSize";
+ private static final String FONT_STYLE_KEY = "fontStyle";
+
+ private static final String NUMBER_FORMATTING_KEY = "numberFormatting";
+
+ private static final String DISPLAY_ATTRIBUTE_KEY = "displayAttribute";
+ private static final String SIGNIFICANT_DIGITS_KEY = "significantDigits";
+
+ // The defaults if there is nothing in the preferences
+ private static String DEFAULT_FONT_NAME = "sansserif";
+ private static int DEFAULT_FONT_SIZE = 6;
+ private static int DEFAULT_FONT_STYLE = Font.PLAIN;
+
+ private static String DEFAULT_NUMBER_FORMATTING = "#.####";
+
+ private static String DECIMAL_NUMBER_FORMATTING = "#.####";
+ private static String SCIENTIFIC_NUMBER_FORMATTING = "0.###E0";
+
+ public LabelPainterController(String title, String key, final LabelPainter labelPainter) {
+
+ this.title = title;
+ this.key = key;
+ this.labelPainter = labelPainter;
+
+ final String defaultFontName = PREFS.get(key + "." + FONT_NAME_KEY, DEFAULT_FONT_NAME);
+ final int defaultFontStyle = PREFS.getInt(key + "." + FONT_SIZE_KEY, DEFAULT_FONT_STYLE);
+ final int defaultFontSize = PREFS.getInt(key + "." + FONT_STYLE_KEY, DEFAULT_FONT_SIZE);
+ final String defaultNumberFormatting = PREFS.get(key + "." + NUMBER_FORMATTING_KEY, DEFAULT_NUMBER_FORMATTING);
+
+ labelPainter.setFont(new Font(defaultFontName, defaultFontStyle, defaultFontSize));
+ labelPainter.setNumberFormat(new DecimalFormat(defaultNumberFormatting));
+
+ optionsPanel = new OptionsPanel();
+
+ titleCheckBox = new JCheckBox(getTitle());
+
+ titleCheckBox.setSelected(labelPainter.isVisible());
+
+ String[] attributes = labelPainter.getAttributes();
+ displayAttributeCombo = new JComboBox(attributes);
+ displayAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayAttributeCombo.getSelectedItem();
+ labelPainter.setDisplayAttribute(attribute);
+ }
+ });
+
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Display:", displayAttributeCombo);
+
+ Font font = labelPainter.getFont();
+ fontSizeSpinner = new JSpinner(new SpinnerNumberModel(font.getSize(), 0.01, 48, 1));
+
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Font Size:", fontSizeSpinner);
+
+ fontSizeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final float size = ((Double) fontSizeSpinner.getValue()).floatValue();
+ Font font = labelPainter.getFont().deriveFont(size);
+ labelPainter.setFont(font);
+ }
+ });
+
+ NumberFormat format = labelPainter.getNumberFormat();
+ int digits = format.getMaximumFractionDigits();
+
+ numericalFormatCombo = new JComboBox(new String[] { "Decimal", "Scientific"});
+ numericalFormatCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String formatType = (String)numericalFormatCombo.getSelectedItem();
+ final int digits = (Integer)digitsSpinner.getValue();
+ if (formatType.equals("Decimal")) {
+ NumberFormat format = new DecimalFormat(DECIMAL_NUMBER_FORMATTING);
+ format.setMaximumFractionDigits(digits);
+ labelPainter.setNumberFormat(format);
+ } else if (formatType.equals("Scientific")) {
+ NumberFormat format = new DecimalFormat(SCIENTIFIC_NUMBER_FORMATTING);
+ format.setMaximumFractionDigits(digits);
+ labelPainter.setNumberFormat(format);
+ }
+ }
+ });
+
+ final JLabel label3 = optionsPanel.addComponentWithLabel("Format:", numericalFormatCombo);
+
+ digitsSpinner = new JSpinner(new SpinnerNumberModel(digits, 2, 14, 1));
+
+ final JLabel label4 = optionsPanel.addComponentWithLabel("Sig. Digits:", digitsSpinner);
+
+ digitsSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int digits = (Integer)digitsSpinner.getValue();
+ NumberFormat format = labelPainter.getNumberFormat();
+ format.setMaximumFractionDigits(digits);
+ labelPainter.setNumberFormat(format);
+ }
+ });
+
+ labelPainter.addPainterListener(new PainterListener() {
+ public void painterChanged() {
+
+ }
+
+ public void painterSettingsChanged() {
+ displayAttributeCombo.removeAllItems();
+ for (String name : labelPainter.getAttributes()) {
+ displayAttributeCombo.addItem(name);
+ }
+
+ optionsPanel.repaint();
+ }
+ });
+
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ fontSizeSpinner.setEnabled(isSelected);
+ label3.setEnabled(isSelected);
+ numericalFormatCombo.setEnabled(isSelected);
+ label4.setEnabled(isSelected);
+ digitsSpinner.setEnabled(isSelected);
+
+ titleCheckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ fontSizeSpinner.setEnabled(isSelected);
+ label3.setEnabled(isSelected);
+ numericalFormatCombo.setEnabled(isSelected);
+ label4.setEnabled(isSelected);
+ digitsSpinner.setEnabled(isSelected);
+ labelPainter.setVisible(isSelected);
+ }
+ });
+
+ }
+
+ public JComponent getTitleComponent() {
+ return titleCheckBox;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ displayAttributeCombo.setSelectedItem(settings.get(key+"."+DISPLAY_ATTRIBUTE_KEY));
+ fontSizeSpinner.setValue((Double)settings.get(key+"."+FONT_SIZE_KEY));
+ digitsSpinner.setValue((Integer)settings.get(key+"."+SIGNIFICANT_DIGITS_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(key+"."+DISPLAY_ATTRIBUTE_KEY, displayAttributeCombo.getSelectedItem().toString());
+ settings.put(key+"."+FONT_SIZE_KEY, fontSizeSpinner.getValue());
+ settings.put(key+"."+SIGNIFICANT_DIGITS_KEY, digitsSpinner.getValue());
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ private final JCheckBox titleCheckBox;
+ private final OptionsPanel optionsPanel;
+
+ private final JComboBox displayAttributeCombo;
+ private final JSpinner fontSizeSpinner;
+
+ private final JComboBox numericalFormatCombo;
+ private final JSpinner digitsSpinner;
+
+ private final String title;
+ private final String key;
+
+ private final LabelPainter labelPainter;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarController.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarController.java
new file mode 100644
index 0000000..c348176
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarController.java
@@ -0,0 +1,136 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.ItemEvent;
+import java.awt.*;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeBarController.java 642 2007-02-16 19:35:15Z rambaut $
+ */
+public class NodeBarController extends AbstractController {
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(NodeBarController.class);
+
+ private static final String NODE_BARS_KEY = "nodeBars";
+
+ private static final String BAR_WIDTH_KEY = "barWidth";
+
+ private static float DEFAULT_BAR_WIDTH = 4.0f;
+
+ public NodeBarController(String title, final NodeBarPainter nodeBarPainter) {
+ this.title = title;
+ this.nodeBarPainter = nodeBarPainter;
+
+ final float defaultBarWidth = PREFS.getFloat(BAR_WIDTH_KEY, DEFAULT_BAR_WIDTH);
+
+ optionsPanel = new OptionsPanel();
+
+ titleCheckBox = new JCheckBox(getTitle());
+
+ titleCheckBox.setSelected(this.nodeBarPainter.isVisible());
+
+ String[] attributeNames = this.nodeBarPainter.getAttributeNames();
+
+ displayAttributeCombo = new JComboBox(attributeNames);
+ displayAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayAttributeCombo.getSelectedItem();
+ nodeBarPainter.setDisplayAttribute(attribute);
+ }
+ });
+
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Display:", displayAttributeCombo);
+
+ this.nodeBarPainter.addPainterListener(new PainterListener() {
+ public void painterChanged() {
+
+ }
+
+ public void painterSettingsChanged() {
+ displayAttributeCombo.removeAllItems();
+ for (String name : nodeBarPainter.getAttributeNames()) {
+ displayAttributeCombo.addItem(name);
+ }
+
+ optionsPanel.repaint();
+ }
+ });
+
+ barWidthSpinner = new JSpinner(new SpinnerNumberModel(defaultBarWidth, 0.01, 48.0, 1.0));
+ barWidthSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ float lineWidth = ((Double) barWidthSpinner.getValue()).floatValue();
+ nodeBarPainter.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ }
+ });
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Bar Width:", barWidthSpinner);
+
+ nodeBarPainter.setStroke(new BasicStroke(defaultBarWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ barWidthSpinner.setEnabled(isSelected);
+
+ titleCheckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ barWidthSpinner.setEnabled(isSelected);
+ nodeBarPainter.setVisible(isSelected);
+ }
+ });
+ }
+
+ public JComponent getTitleComponent() {
+ return titleCheckBox;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ barWidthSpinner.setValue((Double)settings.get(NODE_BARS_KEY + "." + BAR_WIDTH_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(NODE_BARS_KEY + "." + BAR_WIDTH_KEY, barWidthSpinner.getValue());
+ }
+
+
+ private final JCheckBox titleCheckBox;
+ private final OptionsPanel optionsPanel;
+
+ private JComboBox displayAttributeCombo;
+
+ public String getTitle() {
+ return title;
+ }
+
+ private final String title;
+
+ private final NodeBarPainter nodeBarPainter;
+
+ private final JSpinner barWidthSpinner;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarPainter.java
new file mode 100644
index 0000000..e4a4463
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeBarPainter.java
@@ -0,0 +1,202 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.gui.trees.treeviewer_dev.TreePane;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeBarPainter.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class NodeBarPainter extends NodePainter {
+
+ public NodeBarPainter() {
+
+ setupAttributes(null);
+ }
+
+ public void setupAttributes(Collection<? extends Tree> trees) {
+ java.util.Set<String> attributeNames = new TreeSet<String>();
+ if (trees != null) {
+ for (Tree tree : trees) {
+ for (Node node : tree.getNodes()) {
+ for (String name : node.getAttributeNames()) {
+ if (!name.startsWith("!")) {
+ Object attr = node.getAttribute(name);
+ if (attr instanceof Object[]) {
+ Object[] array = (Object[])attr;
+ if (array.length == 2 &&
+ array[0] instanceof Double &&
+ array[1] instanceof Double) {
+ attributeNames.add(name);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (attributeNames.size() == 0) {
+ attributeNames.add("no attributes");
+ }
+
+ this.attributeNames = new String[attributeNames.size()];
+ attributeNames.toArray(this.attributeNames);
+
+ firePainterSettingsChanged();
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Line2D getNodeBar() {
+ return nodeBar;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, Node node) {
+ RootedTree tree = treePane.getTree();
+
+ nodeBar = null;
+
+ Line2D barPath = treePane.getTreeLayoutCache().getNodeBarPath(node);
+ if (barPath != null) {
+
+ double height = tree.getHeight(node);
+ double upper = height;
+ double lower = height;
+
+ boolean hasBar = false;
+ Object[] values = (Object[])node.getAttribute(displayAttribute);
+ if (values != null) {
+ Object value = values[0];
+ if (value != null ) {
+ if (value instanceof Number) {
+ lower = ((Number)value).doubleValue();
+ } else {
+ lower = Double.parseDouble(value.toString());
+ }
+ hasBar = true;
+ } else {
+ // todo - warn the user somehow?
+ }
+
+ value = values[1];
+ if (value != null ) {
+ if (value instanceof Number) {
+ upper = ((Number)value).doubleValue();
+ } else {
+ upper = Double.parseDouble(value.toString());
+ }
+ hasBar = true;
+ } else {
+ // todo - warn the user somehow?
+ }
+ }
+
+
+ if (hasBar) {
+ // x1,y1 is the node point
+ double x1 = barPath.getX1();
+ double y1 = barPath.getY1();
+ // x2,y2 is 1.0 units heigher than the node
+ double x2 = barPath.getX2();
+ double y2 = barPath.getY2();
+
+ // dx,dy is the change in x,y for one unit of height
+ double dx = x2 - x1;
+ double dy = y2 - y1;
+
+ double h1 = lower - height;
+ double h2 = upper - height;
+
+ nodeBar = new Line2D.Double(
+ x1 + (dx * h1), y1 + (dy * h1),
+ x1 + (dx * h2), y1 + (dy * h2));
+
+ }
+ }
+
+ if (nodeBar == null) {
+ return new Rectangle2D.Double(0,0,0,0);
+ }
+
+ return nodeBar.getBounds2D();
+ }
+
+ public double getPreferredWidth() {
+ return 1.0;
+ }
+
+ public double getPreferredHeight() {
+ return 1.0;
+ }
+
+ public double getHeightBound() {
+ return 1.0;
+ }
+
+ /**
+ * The bounds define the shape of the nodeBar so just draw it
+ * @param g2
+ * @param node
+ * @param justification
+ * @param barShape
+ */
+ public void paint(Graphics2D g2, Node node, Justification justification, Shape barShape) {
+ if (barShape != null) {
+
+ Stroke stroke = getStroke();
+ Shape strokedOutline = stroke.createStrokedShape(barShape);
+
+ g2.setPaint(getForeground());
+ g2.fill(strokedOutline);
+
+ g2.setPaint(Color.black);
+ g2.setStroke(new BasicStroke(0.5F));
+
+ g2.draw(strokedOutline);
+ }
+
+ }
+
+ /**
+ * The bounds define the shape of the nodeBar so just draw it
+ * @param g2
+ * @param node
+ * @param justification
+ * @param bounds
+ */
+ public void paint(Graphics2D g2, Node node, Justification justification, Rectangle2D bounds) {
+ throw new UnsupportedOperationException("This version of paint is not used in NodeBarPainter");
+ }
+
+ public String[] getAttributeNames() {
+ return attributeNames;
+ }
+
+ public String getDisplayAttributeName() {
+ return displayAttribute;
+ }
+
+ public void setDisplayAttribute(String displayAttribute) {
+ this.displayAttribute = displayAttribute;
+ firePainterChanged();
+ }
+
+ private double preferredWidth;
+ private double preferredHeight;
+
+ private String displayAttribute = null;
+ private String[] attributeNames;
+
+ private TreePane treePane;
+
+ private Line2D nodeBar = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistController.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistController.java
new file mode 100644
index 0000000..cb97a27
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistController.java
@@ -0,0 +1,136 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.ItemEvent;
+import java.awt.*;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeBarController.java 642 2007-02-16 19:35:15Z rambaut $
+ */
+public class NodeHistController extends AbstractController {
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(NodeHistController.class);
+
+ private static final String NODE_BARS_KEY = "nodeBars";
+
+ private static final String BAR_WIDTH_KEY = "barWidth";
+
+ private static float DEFAULT_BAR_WIDTH = 4.0f;
+
+ public NodeHistController(String title, final NodeHistPainter nodeHistPainter) {
+ this.title = title;
+ this.nodeHistPainter = nodeHistPainter;
+
+ final float defaultBarWidth = PREFS.getFloat(BAR_WIDTH_KEY, DEFAULT_BAR_WIDTH);
+
+ optionsPanel = new OptionsPanel();
+
+ titleCheckBox = new JCheckBox(getTitle());
+
+ titleCheckBox.setSelected(this.nodeHistPainter.isVisible());
+
+ String[] attributeNames = this.nodeHistPainter.getAttributeNames();
+
+ displayAttributeCombo = new JComboBox(attributeNames);
+ displayAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayAttributeCombo.getSelectedItem();
+ nodeHistPainter.setDisplayAttribute(attribute);
+ }
+ });
+
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Display:", displayAttributeCombo);
+
+ this.nodeHistPainter.addPainterListener(new PainterListener() {
+ public void painterChanged() {
+
+ }
+
+ public void painterSettingsChanged() {
+ displayAttributeCombo.removeAllItems();
+ for (String name : nodeHistPainter.getAttributeNames()) {
+ displayAttributeCombo.addItem(name);
+ }
+
+ optionsPanel.repaint();
+ }
+ });
+
+ barWidthSpinner = new JSpinner(new SpinnerNumberModel(defaultBarWidth, 0.01, 48.0, 1.0));
+ barWidthSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ float lineWidth = ((Double) barWidthSpinner.getValue()).floatValue();
+ nodeHistPainter.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ }
+ });
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Bar Width:", barWidthSpinner);
+
+ nodeHistPainter.setStroke(new BasicStroke(defaultBarWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ barWidthSpinner.setEnabled(isSelected);
+
+ titleCheckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean isSelected = titleCheckBox.isSelected();
+ label1.setEnabled(isSelected);
+ displayAttributeCombo.setEnabled(isSelected);
+ label2.setEnabled(isSelected);
+ barWidthSpinner.setEnabled(isSelected);
+ nodeHistPainter.setVisible(isSelected);
+ }
+ });
+ }
+
+ public JComponent getTitleComponent() {
+ return titleCheckBox;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ barWidthSpinner.setValue((Double)settings.get(NODE_BARS_KEY + "." + BAR_WIDTH_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(NODE_BARS_KEY + "." + BAR_WIDTH_KEY, barWidthSpinner.getValue());
+ }
+
+
+ private final JCheckBox titleCheckBox;
+ private final OptionsPanel optionsPanel;
+
+ private JComboBox displayAttributeCombo;
+
+ public String getTitle() {
+ return title;
+ }
+
+ private final String title;
+
+ private final NodeHistPainter nodeHistPainter;
+
+ private final JSpinner barWidthSpinner;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistPainter.java
new file mode 100644
index 0000000..9b233f6
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeHistPainter.java
@@ -0,0 +1,202 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.gui.trees.treeviewer_dev.TreePane;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeBarPainter.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class NodeHistPainter extends NodePainter {
+
+ public NodeHistPainter() {
+
+ setupAttributes(null);
+ }
+
+ public void setupAttributes(Collection<? extends Tree> trees) {
+ java.util.Set<String> attributeNames = new TreeSet<String>();
+ if (trees != null) {
+ for (Tree tree : trees) {
+ for (Node node : tree.getNodes()) {
+ for (String name : node.getAttributeNames()) {
+ if (!name.startsWith("!")) {
+ Object attr = node.getAttribute(name);
+ if (attr instanceof Object[]) {
+ Object[] array = (Object[])attr;
+ if (array.length == 2 &&
+ array[0] instanceof Double &&
+ array[1] instanceof Double) {
+ attributeNames.add(name);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (attributeNames.size() == 0) {
+ attributeNames.add("no attributes");
+ }
+
+ this.attributeNames = new String[attributeNames.size()];
+ attributeNames.toArray(this.attributeNames);
+
+ firePainterSettingsChanged();
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Line2D getNodeBar() {
+ return nodeBar;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, Node node) {
+ RootedTree tree = treePane.getTree();
+
+ nodeBar = null;
+
+ Line2D barPath = treePane.getTreeLayoutCache().getNodeBarPath(node);
+ if (barPath != null) {
+
+ double height = tree.getHeight(node);
+ double upper = height;
+ double lower = height;
+
+ boolean hasBar = false;
+ Object[] values = (Object[])node.getAttribute(displayAttribute);
+ if (values != null) {
+ Object value = values[0];
+ if (value != null ) {
+ if (value instanceof Number) {
+ lower = ((Number)value).doubleValue();
+ } else {
+ lower = Double.parseDouble(value.toString());
+ }
+ hasBar = true;
+ } else {
+ // todo - warn the user somehow?
+ }
+
+ value = values[1];
+ if (value != null ) {
+ if (value instanceof Number) {
+ upper = ((Number)value).doubleValue();
+ } else {
+ upper = Double.parseDouble(value.toString());
+ }
+ hasBar = true;
+ } else {
+ // todo - warn the user somehow?
+ }
+ }
+
+
+ if (hasBar) {
+ // x1,y1 is the node point
+ double x1 = barPath.getX1();
+ double y1 = barPath.getY1();
+ // x2,y2 is 1.0 units heigher than the node
+ double x2 = barPath.getX2();
+ double y2 = barPath.getY2();
+
+ // dx,dy is the change in x,y for one unit of height
+ double dx = x2 - x1;
+ double dy = y2 - y1;
+
+ double h1 = lower - height;
+ double h2 = upper - height;
+
+ nodeBar = new Line2D.Double(
+ x1 + (dx * h1), y1 + (dy * h1),
+ x1 + (dx * h2), y1 + (dy * h2));
+
+ }
+ }
+
+ if (nodeBar == null) {
+ return new Rectangle2D.Double(0,0,0,0);
+ }
+
+ return nodeBar.getBounds2D();
+ }
+
+ public double getPreferredWidth() {
+ return 1.0;
+ }
+
+ public double getPreferredHeight() {
+ return 1.0;
+ }
+
+ public double getHeightBound() {
+ return 1.0;
+ }
+
+ /**
+ * The bounds define the shape of the nodeBar so just draw it
+ * @param g2
+ * @param node
+ * @param justification
+ * @param barShape
+ */
+ public void paint(Graphics2D g2, Node node, Justification justification, Shape barShape) {
+ if (barShape != null) {
+
+ Stroke stroke = getStroke();
+ Shape strokedOutline = stroke.createStrokedShape(barShape);
+
+ g2.setPaint(getForeground());
+ g2.fill(strokedOutline);
+
+ g2.setPaint(Color.black);
+ g2.setStroke(new BasicStroke(0.5F));
+
+ g2.draw(strokedOutline);
+ }
+
+ }
+
+ /**
+ * The bounds define the shape of the nodeBar so just draw it
+ * @param g2
+ * @param node
+ * @param justification
+ * @param bounds
+ */
+ public void paint(Graphics2D g2, Node node, Justification justification, Rectangle2D bounds) {
+ throw new UnsupportedOperationException("This version of paint is not used in NodeBarPainter");
+ }
+
+ public String[] getAttributeNames() {
+ return attributeNames;
+ }
+
+ public String getDisplayAttributeName() {
+ return displayAttribute;
+ }
+
+ public void setDisplayAttribute(String displayAttribute) {
+ this.displayAttribute = displayAttribute;
+ firePainterChanged();
+ }
+
+ private double preferredWidth;
+ private double preferredHeight;
+
+ private String displayAttribute = null;
+ private String[] attributeNames;
+
+ private TreePane treePane;
+
+ private Line2D nodeBar = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodePainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodePainter.java
new file mode 100644
index 0000000..91750f9
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodePainter.java
@@ -0,0 +1,79 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.graphs.Node;
+
+import java.awt.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodePainter.java 373 2006-07-01 15:18:27Z rambaut $
+ */
+public abstract class NodePainter extends AbstractPainter<Node> {
+
+
+ protected NodePainter() {
+ }
+
+ // Getters
+
+ public Stroke getStroke() {
+ return stroke;
+ }
+
+ public Paint getForeground() {
+ return foreground;
+ }
+
+ public Paint getBackground() {
+ return background;
+ }
+
+ public Paint getBorderPaint() {
+ return borderPaint;
+ }
+
+ public Stroke getBorderStroke() {
+ return borderStroke;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ // Setters
+
+ public void setStroke(Stroke stroke) {
+ this.stroke = stroke;
+ firePainterChanged();
+ }
+
+ public void setBackground(Paint background) {
+ this.background = background;
+ firePainterChanged();
+ }
+
+ public void setBorder(Paint borderPaint, Stroke borderStroke) {
+ this.borderPaint = borderPaint;
+ this.borderStroke = borderStroke;
+ firePainterChanged();
+ }
+
+ public void setForeground(Paint foreground) {
+ this.foreground = foreground;
+ firePainterChanged();
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ firePainterChanged();
+ }
+
+ private Stroke stroke = new BasicStroke(1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+ private Paint foreground = Color.BLACK;
+ private Paint background = null;
+ private Paint borderPaint = null;
+ private Stroke borderStroke = null;
+
+ private boolean visible = true;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapeController.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapeController.java
new file mode 100644
index 0000000..ac0345c
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapeController.java
@@ -0,0 +1,166 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeShapeController.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class NodeShapeController extends AbstractController {
+
+ public enum NodePainterType {
+ BAR("Bar"),
+ SHAPE("Shape");
+
+ NodePainterType(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+
+ public NodeShapeController(String title, final NodeShapePainter nodeShapePainter) {
+ this.title = title;
+ this.nodeShapePainter = nodeShapePainter;
+
+ optionsPanel = new OptionsPanel();
+
+ titleCheckBox = new JCheckBox(getTitle());
+ titleCheckBox.setSelected(this.nodeShapePainter.isVisible());
+
+ titleCheckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean selected = titleCheckBox.isSelected();
+ nodeShapePainter.setVisible(selected);
+ }
+ });
+
+ shapeCombo = new JComboBox(new NodePainterType[] {
+ NodePainterType.BAR,
+ NodePainterType.SHAPE
+ });
+
+ String[] attributes = this.nodeShapePainter.getAttributeNames();
+
+ displayAttributeCombo = new JComboBox(attributes);
+ displayAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayAttributeCombo.getSelectedItem();
+ nodeShapePainter.setDisplayAttribute(NodeShapePainter.LOWER_ATTRIBUTE, attribute);
+ }
+ });
+
+ displayLowerAttributeCombo = new JComboBox(attributes);
+ displayLowerAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayLowerAttributeCombo.getSelectedItem();
+ nodeShapePainter.setDisplayAttribute(NodeShapePainter.LOWER_ATTRIBUTE, attribute);
+ }
+ });
+
+ displayUpperAttributeCombo = new JComboBox(attributes);
+ displayUpperAttributeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ String attribute = (String)displayUpperAttributeCombo.getSelectedItem();
+ nodeShapePainter.setDisplayAttribute(NodeShapePainter.UPPER_ATTRIBUTE, attribute);
+ }
+ });
+
+ this.nodeShapePainter.addPainterListener(new PainterListener() {
+ public void painterChanged() {
+
+ }
+
+ public void painterSettingsChanged() {
+ displayLowerAttributeCombo.removeAllItems();
+ displayUpperAttributeCombo.removeAllItems();
+ for (String name : nodeShapePainter.getAttributeNames()) {
+ displayLowerAttributeCombo.addItem(name);
+ displayUpperAttributeCombo.addItem(name);
+ }
+
+ optionsPanel.repaint();
+ }
+ });
+ setupOptions();
+
+ shapeCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ setupOptions();
+ optionsPanel.validate();
+ }
+ });
+
+ }
+
+ private void setupOptions() {
+ optionsPanel.removeAll();
+ optionsPanel.addComponentWithLabel("Shape:", shapeCombo);
+ switch ((NodePainterType) shapeCombo.getSelectedItem()) {
+ case BAR:
+ optionsPanel.addComponentWithLabel("Lower:", displayLowerAttributeCombo);
+ optionsPanel.addComponentWithLabel("Upper:", displayUpperAttributeCombo);
+
+ break;
+
+ case SHAPE:
+ optionsPanel.addComponentWithLabel("Radius:", displayAttributeCombo);
+ break;
+ }
+ fireControllerChanged();
+ }
+
+ public JComponent getTitleComponent() {
+ return titleCheckBox;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ }
+
+ private final JCheckBox titleCheckBox;
+ private final OptionsPanel optionsPanel;
+
+ private JComboBox shapeCombo;
+ private JComboBox displayAttributeCombo;
+ private JComboBox displayLowerAttributeCombo;
+ private JComboBox displayUpperAttributeCombo;
+
+ public String getTitle() {
+ return title;
+ }
+
+ private final String title;
+
+ private final NodeShapePainter nodeShapePainter;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapePainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapePainter.java
new file mode 100644
index 0000000..2f11c73
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/NodeShapePainter.java
@@ -0,0 +1,137 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+import jebl.gui.trees.treeviewer_dev.TreePane;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Point2D;
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: NodeShapePainter.java 536 2006-11-21 16:10:24Z rambaut $
+ */
+public class NodeShapePainter extends NodePainter {
+
+ public static final String AREA_ATTRIBUTE = "area";
+ public static final String RADIUS_ATTRIBUTE = "radius";
+
+ public static final String WIDTH_ATTRIBUTE = "width";
+ public static final String HEIGHT_ATTRIBUTE = "height";
+
+ public static final String LOWER_ATTRIBUTE = "lower";
+ public static final String UPPER_ATTRIBUTE = "upper";
+
+ public enum NodeShape {
+ CIRCLE("Circle"),
+ RECTANGLE("Rectangle");
+
+ NodeShape(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ private final String name;
+ }
+
+ public NodeShapePainter() {
+
+ setupAttributes(null);
+ }
+
+ public void setupAttributes(Tree tree) {
+ java.util.List<String> attributeNames = new ArrayList<String>();
+ if (tree != null) {
+ Set<String> nodeAttributes = new TreeSet<String>();
+ for (Node node : tree.getNodes()) {
+ nodeAttributes.addAll(node.getAttributeNames());
+ }
+ attributeNames.addAll(nodeAttributes);
+ }
+
+ this.attributes = new String[attributeNames.size()];
+ attributeNames.toArray(this.attributes);
+
+ firePainterSettingsChanged();
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, Node item) {
+ RootedTree tree = treePane.getTree();
+ Point2D nodePoint = treePane.getTreeLayoutCache().getNodePoint(item);
+
+ preferredWidth = 20;
+ preferredHeight = 20;
+
+ return new Rectangle2D.Double(nodePoint.getX() - 10, nodePoint.getY() - 10, preferredWidth, preferredHeight);
+ }
+
+ public double getPreferredWidth() {
+ return preferredWidth;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight;
+ }
+
+ /**
+ * The bounds define the shape of the bar so just draw it
+ * @param g2
+ * @param item
+ * @param justification
+ * @param bounds
+ */
+ public void paint(Graphics2D g2, Node item, Justification justification, Rectangle2D bounds) {
+ if (getBackground() != null) {
+ g2.setPaint(getBackground());
+ g2.fill(bounds);
+ }
+
+ if (getBorderPaint() != null && getBorderStroke() != null) {
+ g2.setPaint(getBorderPaint());
+ g2.setStroke(getBorderStroke());
+ }
+
+ g2.draw(bounds);
+ }
+
+ public String[] getAttributeNames() {
+ return attributes;
+ }
+
+ public void setDisplayAttribute(String display, String attribute) {
+ displayAttributes.put(display, attribute);
+ firePainterChanged();
+ }
+
+ public void setDisplayValues(String display, double value) {
+ displayValues.put(display, new Double(value));
+ firePainterChanged();
+ }
+
+ private double preferredWidth;
+ private double preferredHeight;
+
+ protected Map<String, String> displayAttributes = new HashMap<String, String>();
+ protected Map<String, Number> displayValues = new HashMap<String, Number>();
+ protected String[] attributes;
+
+ protected TreePane treePane;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/Painter.java b/src/jebl/gui/trees/treeviewer_dev/painters/Painter.java
new file mode 100644
index 0000000..5de9b44
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/Painter.java
@@ -0,0 +1,69 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.gui.trees.treeviewer_dev.TreePane;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+
+/**
+ * A painter draws a particular decoration onto the tree within a
+ * rectangle.
+ * @author Andrew Rambaut
+ * @version $Id: Painter.java 370 2006-06-29 18:57:56Z rambaut $
+ */
+public interface Painter<T> {
+
+ public enum Orientation {
+ TOP,
+ LEFT,
+ BOTTOM,
+ RIGHT
+ }
+
+ public enum Justification {
+ FLUSH,
+ LEFT,
+ RIGHT,
+ CENTER
+ }
+
+ /**
+ * Called when the painter is installed in a TreePane. Gives the
+ * painter a handle on the TreePane so that it get additional
+ * information.
+ * @param treePane
+ */
+ void setTreePane(TreePane treePane);
+
+ /**
+ * If this is false then the painter should not be displayed.
+ * @return is visible?
+ */
+ boolean isVisible();
+
+ /**
+ * Called to calibrate the painters for a given graphics context. This should
+ * work out the preferred width and height (perhaps for the current font).
+ * @param g2
+ * @param item
+ */
+ Rectangle2D calibrate(Graphics2D g2, T item);
+
+ /**
+ * Called to actually paint into the current graphics context. The painter should
+ * respect the bounds.
+ * @param g2
+ * @param item
+ * @param justification
+ * @param bounds
+ */
+ void paint(Graphics2D g2, T item, Justification justification, Rectangle2D bounds);
+
+ double getPreferredWidth();
+ double getPreferredHeight();
+ double getHeightBound();
+
+ void addPainterListener(PainterListener listener);
+ void removePainterListener(PainterListener listener);
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/PainterListener.java b/src/jebl/gui/trees/treeviewer_dev/painters/PainterListener.java
new file mode 100644
index 0000000..e823222
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/PainterListener.java
@@ -0,0 +1,13 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PainterListener.java 308 2006-05-01 21:15:41Z rambaut $
+ */
+public interface PainterListener {
+
+ void painterChanged();
+
+ void painterSettingsChanged();
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/ScaleAxisPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleAxisPainter.java
new file mode 100644
index 0000000..672ce53
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleAxisPainter.java
@@ -0,0 +1,313 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.gui.trees.treeviewer_dev.TreePane;
+import jebl.evolution.trees.RootedTree;
+import jebl.evolution.trees.Tree;
+
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Line2D;
+import java.awt.*;
+import java.util.Collection;
+
+import org.virion.jam.controlpalettes.ControlPalette;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ScaleBarPainter.java,v 1.7 2006/11/21 16:10:24 rambaut Exp $
+ */
+public class ScaleAxisPainter extends LabelPainter<TreePane> {
+
+ public ScaleAxisPainter() {
+ this(0.0);
+ }
+
+ public ScaleAxisPainter(double scaleRange) {
+ this.scaleRange = scaleRange;
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, TreePane treePane) {
+ Font oldFont = g2.getFont();
+ g2.setFont(getFont());
+
+ FontMetrics fm = g2.getFontMetrics();
+ double labelHeight = fm.getHeight();
+
+ preferredWidth = treePane.getTreeScale() * scaleRange;
+ preferredHeight = labelHeight + topMargin + bottomMargin + scaleBarStroke.getLineWidth();
+
+ yOffset = (float) (fm.getAscent() + topMargin + bottomMargin) + scaleBarStroke.getLineWidth();
+
+ g2.setFont(oldFont);
+
+ return new Rectangle2D.Double(0.0, 0.0, preferredWidth, preferredHeight);
+ }
+
+ public void paint(Graphics2D g2, TreePane treePane, Justification justification, Rectangle2D bounds) {
+ Font oldFont = g2.getFont();
+ Paint oldPaint = g2.getPaint();
+ Stroke oldStroke = g2.getStroke();
+
+ if (getBackground() != null) {
+ g2.setPaint(getBackground());
+ g2.fill(bounds);
+ }
+
+ if (getBorderPaint() != null && getBorderStroke() != null) {
+ g2.setPaint(getBorderPaint());
+ g2.setStroke(getBorderStroke());
+ g2.draw(bounds);
+ }
+
+ g2.setFont(getFont());
+
+ // we don't need accuracy but a nice short number
+ final String label = Double.toString(scaleRange);
+
+ Rectangle2D rect = g2.getFontMetrics().getStringBounds(label, g2);
+
+ double x1, x2;
+ float xOffset;
+ switch (justification) {
+ case CENTER:
+ xOffset = (float) (bounds.getX() + (bounds.getWidth() - rect.getWidth()) / 2.0);
+ x1 = (bounds.getX() + (bounds.getWidth() - preferredWidth) / 2.0);
+ x2 = x1 + preferredWidth;
+ break;
+ case FLUSH:
+ case LEFT:
+ xOffset = (float) bounds.getX();
+ x1 = bounds.getX();
+ x2 = x1 + preferredWidth;
+ break;
+ case RIGHT:
+ xOffset = (float) (bounds.getX() + bounds.getWidth() - rect.getWidth());
+ x2 = bounds.getX() + bounds.getWidth();
+ x1 = x2 - preferredWidth;
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alignment enum option");
+ }
+
+ g2.setPaint(getForeground());
+ g2.setStroke(getScaleBarStroke());
+
+ g2.draw(new Line2D.Double(x1, bounds.getY() + topMargin, x2, bounds.getY() + topMargin));
+
+ g2.drawString(label, xOffset, yOffset + (float) bounds.getY());
+
+ g2.setFont(oldFont);
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+ }
+ /**
+ * Get the maximum width of the labels of an axis
+ */
+ protected double getMaxTickLabelWidth(Graphics2D g2)
+ {
+ String label;
+ double width;
+ double maxWidth = 0;
+
+ if (axis.getLabelFirst()) { // Draw first minor tick as a major one (with a label)
+ label = axis.getFormatter().format(axis.getMinorTickValue(0, -1));
+ width = g2.getFontMetrics().stringWidth(label);
+ if (maxWidth < width)
+ maxWidth = width;
+ }
+ int n = axis.getMajorTickCount();
+ for (int i = 0; i < n; i++) {
+ label = axis.getFormatter().format(axis.getMajorTickValue(i));
+ width = g2.getFontMetrics().stringWidth(label);
+ if (maxWidth < width)
+ maxWidth = width;
+ }
+ if (axis.getLabelLast()) { // Draw first minor tick as a major one (with a label)
+ label = axis.getFormatter().format(axis.getMinorTickValue(0, n - 1));
+ width = g2.getFontMetrics().stringWidth(label);
+ if (maxWidth < width)
+ maxWidth = width;
+ }
+
+ return maxWidth;
+ }
+
+// protected void paintAxis(Graphics2D g2)
+// {
+// int n1 = axis.getMajorTickCount();
+// int n2, i, j;
+//
+// n2 = axis.getMinorTickCount(-1);
+// if (axis.getLabelFirst()) { // Draw first minor tick as a major one (with a label)
+//
+// paintMajorTick(g2, axis.getMinorTickValue(0, -1));
+//
+// for (j = 1; j < n2; j++) {
+// paintMinorTick(g2, axis.getMinorTickValue(j, -1));
+// }
+// } else {
+//
+// for (j = 0; j < n2; j++) {
+// paintMinorTick(g2, axis.getMinorTickValue(j, -1));
+// }
+// }
+//
+// for (i = 0; i < n1; i++) {
+//
+// paintMajorTick(g2, axis.getMajorTickValue(i));
+// n2 = axis.getMinorTickCount(i);
+//
+// if (i == (n1-1) && axis.getLabelLast()) { // Draw last minor tick as a major one
+//
+// paintMajorTick(g2, axis.getMinorTickValue(0, i));
+//
+// for (j = 1; j < n2; j++) {
+// paintMinorTick(g2, axis.getMinorTickValue(j, i));
+// }
+// } else {
+//
+// for (j = 0; j < n2; j++) {
+// paintMinorTick(g2, axis.getMinorTickValue(j, i));
+// }
+// }
+// }
+// }
+//
+// protected void paintMajorTick(Graphics2D g2, double value)
+// {
+// g2.setPaint(getForeground());
+// g2.setStroke(getScaleBarStroke());
+//
+// String label = axis.getFormatter().format(value);
+// double pos = transformX(value);
+//
+// Line2D line = new Line2D.Double(pos, plotBounds.getMaxY(), pos, plotBounds.getMaxY() + majorTickSize);
+// g2.draw(line);
+//
+// g2.setPaint(getForeground());
+// double width = g2.getFontMetrics().stringWidth(label);
+// g2.drawString(label, (float)(pos - (width / 2)), (float)(plotBounds.getMaxY() + (majorTickSize * 1.25) + xTickLabelOffset));
+// }
+//
+// protected void paintMinorTick(Graphics2D g2, double value)
+// {
+//
+// g2.setPaint(getForeground());
+// g2.setStroke(getScaleBarStroke());
+//
+// double pos = transformX(value);
+//
+// Line2D line = new Line2D.Double(pos, plotBounds.getMaxY(), pos, plotBounds.getMaxY() + minorTickSize);
+// g2.draw(line);
+// }
+//
+// /**
+// * Transform a chart co-ordinates into a drawing co-ordinates
+// */
+// protected double transformX(double value) {
+// return ((axis.transform(value) - axis.transform(axis.getMinAxis())) * xScale) + xOffset;
+// }
+//
+
+ public double getPreferredWidth() {
+ return preferredWidth;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight + yOffset;
+ }
+
+ public BasicStroke getScaleBarStroke() {
+ return scaleBarStroke;
+ }
+
+ public void setScaleBarStroke(BasicStroke scaleBarStroke) {
+ this.scaleBarStroke = scaleBarStroke;
+ firePainterChanged();
+ }
+
+ public double getScaleRange() {
+ return scaleRange;
+ }
+
+ public void setScaleRange(double scaleRange) {
+ this.userScaleRange = scaleRange;
+ calculateScaleRange();
+ firePainterChanged();
+ }
+
+ public void setAutomaticScale(boolean automaticScale) {
+ this.automaticScale = automaticScale;
+ calculateScaleRange();
+ firePainterChanged();
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // nothing to do
+ }
+
+ public void calculateScaleRange() {
+ if( !automaticScale && userScaleRange != 0.0 ) {
+ scaleRange = userScaleRange;
+ } else {
+ RootedTree tree = treePane.getTree();
+ if (tree != null) {
+ final double treeHeight = tree.getHeight(tree.getRootNode());
+
+ if( treeHeight == 0.0 ) {
+ scaleRange = 0.0;
+ } else {
+
+ double low = treeHeight / 10.0;
+ double b = -(Math.ceil(Math.log10(low)) - 1);
+ for(int n = 0; n < 3; ++n) {
+ double factor = Math.pow(10, b);
+ double x = ((int)(low * factor) + 1)/factor;
+ if( n == 2 || x < treeHeight / 5.0 ) {
+ scaleRange = x;
+ break;
+ }
+ ++b;
+ }
+ }
+ }
+ }
+
+ }
+
+ public String[] getAttributes() {
+ return new String[0];
+ }
+
+ public void setupAttributes(Collection<? extends Tree> trees) {
+ // nothing to do...
+ }
+
+ public void setDisplayAttribute(String displayAttribute) {
+ }
+
+ private BasicStroke scaleBarStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+
+ private ScaleBarAxis axis = new ScaleBarAxis(ScaleBarAxis.AT_DATA, ScaleBarAxis.AT_DATA);
+
+ private double scaleRange;
+ private double topMargin = 4.0;
+ private double bottomMargin = 4.0;
+ private double userScaleRange = 0.0;
+ private boolean automaticScale = true;
+
+ private double preferredHeight;
+ private double preferredWidth;
+
+ private float yOffset;
+
+ protected TreePane treePane;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarAxis.java b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarAxis.java
new file mode 100644
index 0000000..d4de19d
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarAxis.java
@@ -0,0 +1,652 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import java.text.DecimalFormat;
+
+public class ScaleBarAxis {
+
+ // The minimum and maximum values of the data
+
+ // These constants are used for automatic scaling to select exactly
+ // where the axis starts and stops.
+ static public final int AT_MAJOR_TICK=0;
+ static public final int AT_MAJOR_TICK_PLUS=1;
+ static public final int AT_MINOR_TICK=2;
+ static public final int AT_MINOR_TICK_PLUS=3;
+ static public final int AT_DATA=4;
+ static public final int AT_ZERO=5;
+ static public final int AT_VALUE=6;
+
+ protected double minData=Double.POSITIVE_INFINITY;
+ protected double maxData=Double.NEGATIVE_INFINITY;// The number of major ticks and minor ticks within them
+ protected int majorTickCount;
+ protected int minorTickCount; // calculated automatically
+ // The prefered minimum number of ticks
+ protected int prefMajorTickCount = 5;
+ protected int prefMinorTickCount = 2; // set manually
+ // Flags using the above constants
+ protected int minAxisFlag;
+ protected int maxAxisFlag;// The distance between major ticks and minor Ticks
+ protected double majorTick;
+ protected double minorTick; // calculated automatically or set by user
+ // The value of the first and last major tick
+ protected double minTick;
+ protected double maxTick; // calculated automatically or set by user
+ // The value of the beginning and end of the axis
+ protected double minAxis;
+ protected double maxAxis;// User defined axis range
+ protected double minValue;
+ protected double maxValue;// Flags to give automatic scaling and integer division
+ protected boolean isAutomatic=true;
+ protected boolean isDiscrete=false;// Flags to specify that the first tick and last tick should have labels.
+ // It is up to the AxisPanel to do something about this.
+ protected boolean labelFirst=false;
+ protected boolean labelLast=false;
+ protected boolean isCalibrated = false;
+ protected DecimalFormat formatter = new DecimalFormat("0.0#######");// Used internally
+ private double epsilon;
+ private int fraction;
+
+ static private final int UNIT=0;
+ static private final int HALFS=1;
+ static private final int QUARTERS=2;
+ static private final int FIFTHS=3;
+
+ /**
+ * Empty constructor
+ */
+ public ScaleBarAxis() { }
+
+ /**
+ * Axis flag constructor
+ */
+ public ScaleBarAxis(int minAxisFlag, int maxAxisFlag) {
+
+ setAxisFlags(minAxisFlag, maxAxisFlag);
+ }
+
+ /**
+ * Transform a value
+ */
+ public double transform(double value) {
+ return value; // a linear transform !
+ }
+
+ /**
+ * Untransform a value
+ */
+ public double untransform(double value) {
+ return value; // a linear transform !
+ }
+
+ /**
+ * Set axis flags
+ */
+ public void setAxisFlags(int minAxisFlag, int maxAxisFlag) {
+ this.minAxisFlag = minAxisFlag;
+ this.maxAxisFlag = maxAxisFlag;
+ isCalibrated = false;
+ }
+
+ /**
+ * Set preferred number of ticks
+ */
+ public void setPrefNumTicks(int prefMajorTickCount, int prefMinorTickCount) {
+ this.prefMajorTickCount = prefMajorTickCount;
+ this.prefMinorTickCount = prefMinorTickCount;
+ isCalibrated = false;
+ }
+
+ /**
+ * Set integer scale
+ */
+ public void setIsDiscrete(boolean isDiscrete) {
+ this.isDiscrete = isDiscrete;
+ isCalibrated = false;
+ }
+
+ /**
+ * Set show label for first tick flag
+ */
+ public void setLabelFirst(boolean labelFirst) {
+ this.labelFirst = labelFirst;
+ }
+
+ /**
+ * Set show label for last tick flag
+ */
+ public void setLabelLast(boolean labelLast) {
+ this.labelLast = labelLast;
+ }
+
+ /**
+ * Set the formatter for the tick labels
+ */
+ public void setFormatter(DecimalFormat formatter) {
+ this.formatter = formatter;
+ }
+
+ /**
+ * return show label for first tick flag
+ */
+ public boolean getLabelFirst() {
+ if (getMinorTickCount(-1)==0) // The first tick is a label anyway
+ return false;
+ else
+ return labelFirst;
+ }
+
+ /**
+ * return show label for last tick flag
+ */
+ public boolean getLabelLast() {
+ if (getMinorTickCount(majorTickCount-1)==0) // The last tick is a label anyway
+ return false;
+ else
+ return labelLast;
+ }
+
+ /**
+ * Manually set the axis range. Axis flags must be set to AT_VALUE for this to take effect.
+ */
+ public void setManualRange(double minValue, double maxValue) {
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+
+ isCalibrated = false;
+ }
+
+ /**
+ * Manually set the axis ticks
+ */
+ public void setManualAxis(double minTick, double maxTick,
+ double majorTick, double minorTick) {
+ this.minTick = minTick;
+ this.maxTick = maxTick;
+
+ this.majorTick = majorTick;
+ this.minorTick = minorTick;
+
+ majorTickCount = (int)((maxTick-minTick)/majorTick)+1; // Add 1 to include the last tick
+ minorTickCount = (int)(majorTick/minorTick)-1; // Sub 1 to exclude the major tick
+ isAutomatic=false;
+ isCalibrated = false;
+ }
+
+ /**
+ * Set the axis to automatic calibration
+ */
+ public void setAutomatic() {
+ setAutomatic(AT_MAJOR_TICK, AT_MAJOR_TICK);
+ }
+
+ /**
+ * Set the axis to automatic calibration
+ */
+ public void setAutomatic(int minAxisFlag, int maxAxisFlag) {
+ setAxisFlags(minAxisFlag, maxAxisFlag);
+ isAutomatic = true;
+ isCalibrated = false;
+ }
+
+ /**
+ * Set the range of the data
+ */
+ public void setRange(double minValue, double maxValue) {
+ if (!Double.isNaN(minValue)) {
+ this.minData = minValue;
+ }
+ if (!Double.isNaN(maxValue)) {
+ this.maxData = maxValue;
+ }
+
+ isCalibrated = false;
+ }
+
+ /**
+ * Adds the range to the existing range, widening if neccessary
+ */
+ public void addRange(double minValue, double maxValue) {
+ if (!Double.isNaN(maxValue) && maxValue > maxData) {
+ maxData = maxValue;
+ }
+ if (!Double.isNaN(minValue) && minValue < minData) {
+ minData = minValue;
+ }
+
+ //System.err.println("addRange("+minValue +", "+maxValue+")");
+ //System.err.println("maxValue = "+maxData);
+ //System.err.println("maxData = "+maxData);
+
+ isCalibrated = false;
+ }
+
+ /**
+ * A static method that uses the natural log to obtain log to base10.
+ * This is required for the linear autoCalibrate but will also be
+ * used by a derived class giving a log transformed axis.
+ */
+ static public double log10(double inValue) {
+ return Math.log(inValue)/Math.log(10.0);
+ }
+
+ public void calibrate() {
+
+ double minValue = minData;
+ double maxValue = maxData;
+
+ if (minAxisFlag== AT_ZERO) {
+ minValue = 0;
+ } else if (minAxisFlag == AT_VALUE) {
+ minValue = this.minValue;
+ }
+
+ if (maxAxisFlag== AT_ZERO) {
+ maxValue = 0;
+ } else if (maxAxisFlag == AT_VALUE) {
+ maxValue = this.maxValue;
+ }
+
+ double range = maxValue - minValue;
+ if (range < 0.0) {
+ range = 0.0;
+ }
+
+ epsilon = range * 1.0E-10;
+
+ if (isAutomatic) {
+ // We must find the optimum minMajorTick and maxMajorTick so
+ // that they contain the data range (minData to maxData) and
+ // are in the right order of magnitude
+
+ if (range < 1.0E-30) {
+ if (minData < 0.0) {
+ majorTick = Math.pow(10.0, Math.floor(log10(Math.abs(minData))));
+ minTick = Math.floor(minData / majorTick) * majorTick;
+ maxTick = 0.0;
+ } else if (minData > 0.0) {
+ majorTick = Math.pow(10.0, Math.floor(log10(Math.abs(minData))));
+ minTick = 0.0;
+ maxTick = Math.ceil(maxData / majorTick) * majorTick;
+ } else {
+ minTick = -1.0;
+ maxTick = 1.0;
+ majorTick = 1.0;
+ }
+
+ minorTick = majorTick;
+ majorTickCount = 1;
+ minorTickCount = 0;
+
+ } else {
+ // First find order of magnitude below the data range...
+ majorTick = Math.pow(10.0, Math.floor(log10(range)));
+
+ calcMinTick();
+ calcMaxTick();
+
+ calcMajorTick();
+ calcMinorTick();
+ }
+ }
+
+ minAxis = minTick;
+ maxAxis = maxTick;
+
+ handleAxisFlags();
+
+ isCalibrated=true;
+ }
+
+ /**
+ * Calculate the optimum minimum tick. Override to change default behaviour
+ */
+ public void calcMinTick() {
+ // Find the nearest multiple of majorTick below minData
+ if (minData == 0.0)
+ minTick = 0;
+ else
+ minTick = Math.floor(minData / majorTick) * majorTick;
+ }
+
+ /**
+ * Calculate the optimum maximum tick. Override to change default behaviour
+ */
+ public void calcMaxTick() {
+ // Find the nearest multiple of majorTick above maxData
+ if (maxData == 0) {
+ maxTick = 0;
+ } else if (maxData < 0.0) {
+ // Added so that negative values are handled correctly -- AJD
+ maxTick = -Math.floor(-maxData / majorTick) * majorTick;
+ } else {
+ maxTick = Math.ceil(maxData / majorTick) * majorTick;
+ }
+ }
+
+ /**
+ * Calculate the optimum major tick distance. Override to change default behaviour
+ */
+ public void calcMajorTick() {
+ fraction= UNIT;
+
+ // make sure that there are at least prefNumMajorTicks major ticks
+ // by dividing up into halves, quarters, fifths or tenths
+ double u=majorTick;
+ double r=maxTick-minTick;
+ majorTickCount=(int)(r/u);
+
+ while (majorTickCount < prefMajorTickCount) {
+ u=majorTick/2; // Try using halves
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ majorTickCount=(int)(r/u);
+ fraction= HALFS;
+ if (majorTickCount >= prefMajorTickCount)
+ break;
+ }
+
+ u=majorTick/4; // Try using quarters
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ majorTickCount=(int)(r/u);
+ fraction= QUARTERS;
+ if (majorTickCount >= prefMajorTickCount)
+ break;
+ }
+
+ u=majorTick/5; // Try using fifths
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ majorTickCount=(int)(r/u);
+ fraction= FIFTHS;
+ if (majorTickCount >= prefMajorTickCount)
+ break;
+ }
+
+ if (isDiscrete && (majorTick/10)!=Math.floor(majorTick/10)) {
+ // majorTick/10 is not an integer so no point in further subdivision
+ u=majorTick;
+ majorTickCount=(int)(r/u);
+ break;
+ }
+
+ majorTick/=10; // finally just divide by ten
+ u=majorTick; // and go back to whole units
+ majorTickCount=(int)(r/u);
+ fraction= UNIT;
+
+ }
+ majorTick=u;
+
+ if (isDiscrete && majorTick<1.0) {
+ majorTick=1.0;
+ majorTickCount=(int)(r/majorTick);
+ fraction= UNIT;
+ }
+
+ majorTickCount++; // Add 1 to give the final tick
+
+ // Trim down any excess major ticks either side of the data range
+ // Epsilon allows for any inprecision in the calculation
+ while ((minTick + majorTick - epsilon)<minData) {
+ minTick+=majorTick;
+ majorTickCount--;
+ }
+ while ((maxTick - majorTick + epsilon)>maxData) {
+ maxTick-=majorTick;
+ majorTickCount--;
+ }
+ }
+
+ /**
+ * Calculate the optimum minor tick distance. Override to change default behaviour
+ */
+ public void calcMinorTick() {
+ minorTick=majorTick; // start with minorTick the same as majorTick
+ double u=minorTick;
+ double r=majorTick;
+ minorTickCount=(int)(r/u);
+
+ while (minorTickCount < prefMinorTickCount) {
+ // if the majorTick was divided as quarters, then we can't
+ // divide the minor ticks into halves or quarters.
+ if (fraction!= QUARTERS) {
+ u=minorTick/2; // Try using halves
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ minorTickCount=(int)(r/u);
+ if (minorTickCount>=prefMinorTickCount)
+ break;
+ }
+
+ u=minorTick/4; // Try using quarters
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ minorTickCount=(int)(r/u);
+ if (minorTickCount>=prefMinorTickCount)
+ break;
+ }
+ }
+
+ u=minorTick/5; // Try using fifths
+ if (!isDiscrete || u==Math.floor(u)) { // u is an integer
+ minorTickCount=(int)(r/u);
+ if (minorTickCount>=prefMinorTickCount)
+ break;
+ }
+
+ if (isDiscrete && (minorTick/10)!=Math.floor(minorTick/10)) {
+ // minorTick/10 is not an integer so no point in further subdivision
+ u=minorTick;
+ minorTickCount=(int)(r/u);
+ break;
+ }
+
+ minorTick/=10; // finally just divide by ten
+ u=minorTick; // and go back to whole units
+ minorTickCount=(int)(r/u);
+ }
+ minorTick=u;
+
+ minorTickCount--;
+ }
+
+ /**
+ * Handles axis flags. Override to change default behaviour
+ */
+ public void handleAxisFlags() {
+ // Now we must honor the min/maxAxisFlag settings
+ if (minAxisFlag== AT_MAJOR_TICK_PLUS || minAxisFlag== AT_MINOR_TICK_PLUS) {
+ if (minAxis==minData) {
+ majorTickCount++;
+ minTick-=majorTick;
+ minAxis=minTick;
+ }
+ }
+
+ if (minAxisFlag== AT_MINOR_TICK_PLUS) {
+ if ((minAxis+minorTick)<minData) {
+ majorTickCount--;
+ minTick+=majorTick;
+ while ((minAxis+minorTick)<minData) {
+ minAxis+=minorTick;
+ }
+ }
+ } else if (minAxisFlag== AT_MINOR_TICK) {
+ if ((minAxis+minorTick)<=minData) {
+ majorTickCount--;
+ minTick+=majorTick;
+ while ((minAxis+minorTick)<=minData) {
+ minAxis+=minorTick;
+ }
+ }
+ } else if (minAxisFlag== AT_DATA) {
+ if (minTick<minData) { // in case minTick==minData
+ majorTickCount--;
+ minTick+=majorTick;
+ }
+ minAxis=minData;
+ } else if (minAxisFlag== AT_VALUE) {
+ if (minTick<minValue) { // in case minTick==minValue
+ majorTickCount--;
+ minTick+=majorTick;
+ }
+ minAxis=minValue;
+ } else if (minAxisFlag== AT_ZERO) {
+ majorTickCount+=(int)(minTick/majorTick);
+ minTick=0;
+ minAxis=0;
+ }
+
+ if (maxAxisFlag== AT_MAJOR_TICK_PLUS || maxAxisFlag== AT_MINOR_TICK_PLUS) {
+ if (maxAxis==maxData) {
+ majorTickCount++;
+ maxTick+=majorTick;
+ maxAxis=maxTick;
+ }
+ }
+
+ if (maxAxisFlag== AT_MINOR_TICK_PLUS) {
+ if ((maxAxis-minorTick)>maxData) {
+ majorTickCount--;
+ maxTick-=majorTick;
+ while ((maxAxis-minorTick)>maxData) {
+ maxAxis-=minorTick;
+ }
+ }
+ } else if (maxAxisFlag== AT_MINOR_TICK) {
+ if ((maxAxis-minorTick)>=maxData) {
+ majorTickCount--;
+ maxTick-=majorTick;
+ while ((maxAxis-minorTick)>=maxData) {
+ maxAxis-=minorTick;
+ }
+ }
+ } else if (maxAxisFlag== AT_DATA) {
+ if (maxTick>maxData) { // in case maxTick==maxData
+ majorTickCount--;
+ maxTick-=majorTick;
+ }
+ maxAxis=maxData;
+ } else if (maxAxisFlag== AT_VALUE) {
+ if (maxTick>maxValue) { // in case maxTick==maxValue
+ majorTickCount--;
+ maxTick-=majorTick;
+ }
+ maxAxis=maxValue;
+ } else if (maxAxisFlag== AT_ZERO) {
+ majorTickCount+=(int)(-maxTick/majorTick);
+ maxTick=0;
+ maxTick=0;
+ }
+ }
+
+ /**
+ * Scale a value to between 0 and 1.
+ */
+ public double scaleValue(double value) {
+ if (!isCalibrated)
+ calibrate();
+
+ double f=(transform(value)-transform(minAxis))/(transform(maxAxis)-transform(minAxis));
+ return f;
+ }
+
+ /**
+ * @return a DecimalFormat for formating the axis labels
+ */
+ public DecimalFormat getFormatter() {
+ return formatter;
+ }
+
+ /**
+ * @return minimum range of the axis
+ */
+ public double getMinAxis() {
+ if (!isCalibrated)
+ calibrate();
+
+ return minAxis;
+ }
+
+ /**
+ * @return maximum range of the axis
+ */
+ public double getMaxAxis() {
+ if (!isCalibrated)
+ calibrate();
+
+ return maxAxis;
+ }
+
+ /**
+ * @return minimum range of the data
+ */
+ public double getMinData() { return minData; }
+
+ /**
+ * @return maximum range of the data
+ */
+ public double getMaxData() { return maxData; }
+
+ /**
+ * Returns the number of major tick marks along the axis
+ */
+ public int getMajorTickCount() {
+ if (!isCalibrated)
+ calibrate();
+ return majorTickCount;
+ }
+
+ /**
+ * Returns the number of minor tick marks within each major one
+ * By default all major ticks have the same number of minor ticks
+ * except the last which has none.
+ */
+ public int getMinorTickCount(int majorTickIndex) {
+ if (!isCalibrated)
+ calibrate();
+
+ if (majorTickIndex == majorTickCount-1)
+ return (int)((maxAxis-maxTick)/minorTick);
+ else if (majorTickIndex==-1)
+ return (int)((minTick-minAxis)/minorTick);
+ else
+ return minorTickCount;
+ }
+
+ /** getMajorTick
+ * Returns the value of the majorTickIndex'th major tick
+ */
+ public double getMajorTickValue(int majorTickIndex) {
+ if (!isCalibrated)
+ calibrate();
+ return (majorTickIndex*majorTick)+minTick;
+ }
+
+ /** getMinorTick
+ * Returns the value of the minorTickIndex'th minor tick
+ */
+ public double getMinorTickValue(int minorTickIndex, int majorTickIndex) {
+ if (!isCalibrated)
+ calibrate();
+ // get minorTickIndex+1 to skip the major tick
+ if (majorTickIndex==-1)
+ return minTick-((minorTickIndex+1)*minorTick);
+ else
+ return ((minorTickIndex+1)*minorTick)+getMajorTickValue(majorTickIndex);
+ }
+
+ /**
+ * @return the spacing between major ticks
+ */
+ public double getMajorTickSpacing() {
+ if (!isCalibrated)
+ calibrate();
+ return majorTick;
+ }
+
+ /**
+ * @return the spacing between minor ticks
+ */
+ public double getMinorTickSpacing() {
+ if (!isCalibrated)
+ calibrate();
+ return minorTick;
+ }
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainter.java b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainter.java
new file mode 100644
index 0000000..587ef63
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainter.java
@@ -0,0 +1,221 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import jebl.evolution.trees.Tree;
+import jebl.evolution.trees.RootedTree;
+import jebl.gui.trees.treeviewer_dev.TreePane;
+import org.virion.jam.controlpalettes.ControlPalette;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ScaleBarPainter.java 642 2007-02-16 19:35:15Z rambaut $
+ */
+public class ScaleBarPainter extends LabelPainter<TreePane> {
+
+ public enum ScaleBarType {
+ BAR,
+ AXIS
+ }
+
+ public ScaleBarPainter() {
+ this(0.0);
+ }
+
+ public ScaleBarPainter(double scaleRange) {
+ this.scaleRange = scaleRange;
+ type = ScaleBarType.BAR;
+ }
+
+ public void setTreePane(TreePane treePane) {
+ this.treePane = treePane;
+ }
+
+ public Rectangle2D calibrate(Graphics2D g2, TreePane treePane) {
+ Font oldFont = g2.getFont();
+ g2.setFont(getFont());
+
+ FontMetrics fm = g2.getFontMetrics();
+ double labelHeight = fm.getHeight();
+
+ preferredWidth = treePane.getTreeScale() * scaleRange;
+ preferredHeight = labelHeight + topMargin + bottomMargin + scaleBarStroke.getLineWidth();
+
+ yOffset = (float) (fm.getAscent() + topMargin + bottomMargin) + scaleBarStroke.getLineWidth();
+
+ g2.setFont(oldFont);
+
+ return new Rectangle2D.Double(0.0, 0.0, preferredWidth, preferredHeight);
+ }
+
+ public void paint(Graphics2D g2, TreePane treePane, Justification justification, Rectangle2D bounds) {
+ Font oldFont = g2.getFont();
+ Paint oldPaint = g2.getPaint();
+ Stroke oldStroke = g2.getStroke();
+
+ if (getBackground() != null) {
+ g2.setPaint(getBackground());
+ g2.fill(bounds);
+ }
+
+ if (getBorderPaint() != null && getBorderStroke() != null) {
+ g2.setPaint(getBorderPaint());
+ g2.setStroke(getBorderStroke());
+ g2.draw(bounds);
+ }
+
+ g2.setFont(getFont());
+
+ // we don't need accuracy but a nice short number
+ final String label = Double.toString(scaleRange);
+
+ Rectangle2D rect = g2.getFontMetrics().getStringBounds(label, g2);
+
+ double x1, x2;
+ float xOffset;
+ switch (justification) {
+ case CENTER:
+ xOffset = (float) (bounds.getX() + (bounds.getWidth() - rect.getWidth()) / 2.0);
+ x1 = (bounds.getX() + (bounds.getWidth() - preferredWidth) / 2.0);
+ x2 = x1 + preferredWidth;
+ break;
+ case FLUSH:
+ case LEFT:
+ xOffset = (float) bounds.getX();
+ x1 = bounds.getX();
+ x2 = x1 + preferredWidth;
+ break;
+ case RIGHT:
+ xOffset = (float) (bounds.getX() + bounds.getWidth() - rect.getWidth());
+ x2 = bounds.getX() + bounds.getWidth();
+ x1 = x2 - preferredWidth;
+ break;
+ default:
+ throw new IllegalArgumentException("Unrecognized alignment enum option");
+ }
+
+ g2.setPaint(getForeground());
+ g2.setStroke(getScaleBarStroke());
+
+ g2.draw(new Line2D.Double(x1, bounds.getY() + topMargin, x2, bounds.getY() + topMargin));
+
+ g2.drawString(label, xOffset, yOffset + (float) bounds.getY());
+
+ g2.setFont(oldFont);
+ g2.setPaint(oldPaint);
+ g2.setStroke(oldStroke);
+ }
+
+ public double getPreferredWidth() {
+ return preferredWidth;
+ }
+
+ public double getPreferredHeight() {
+ return preferredHeight;
+ }
+
+ public double getHeightBound() {
+ return preferredHeight + yOffset;
+ }
+
+ public ScaleBarType getType() {
+ return type;
+ }
+
+ public void setType(ScaleBarType type) {
+ this.type = type;
+ firePainterChanged();
+
+ }
+
+ public BasicStroke getScaleBarStroke() {
+ return scaleBarStroke;
+ }
+
+ public void setScaleBarStroke(BasicStroke scaleBarStroke) {
+ this.scaleBarStroke = scaleBarStroke;
+ firePainterChanged();
+ }
+
+ public double getScaleRange() {
+ return scaleRange;
+ }
+
+ public void setScaleRange(double scaleRange) {
+ this.userScaleRange = scaleRange;
+ calculateScaleRange();
+ firePainterChanged();
+ }
+
+ public void setAutomaticScale(boolean automaticScale) {
+ this.automaticScale = automaticScale;
+ calculateScaleRange();
+ firePainterChanged();
+ }
+
+ public void setControlPalette(ControlPalette controlPalette) {
+ // nothing to do
+ }
+
+ public void calculateScaleRange() {
+ if( !automaticScale && userScaleRange != 0.0 ) {
+ scaleRange = userScaleRange;
+ } else {
+ RootedTree tree = treePane.getTree();
+ if (tree != null) {
+ final double treeHeight = tree.getHeight(tree.getRootNode());
+
+ if( treeHeight == 0.0 ) {
+ scaleRange = 0.0;
+ } else {
+
+ double low = treeHeight / 10.0;
+ double b = -(Math.ceil(Math.log10(low)) - 1);
+ for(int n = 0; n < 3; ++n) {
+ double factor = Math.pow(10, b);
+ double x = ((int)(low * factor) + 1)/factor;
+ if( n == 2 || x < treeHeight / 5.0 ) {
+ scaleRange = x;
+ break;
+ }
+ ++b;
+ }
+ }
+ }
+ }
+
+ }
+
+ public String[] getAttributes() {
+ return new String[0];
+ }
+
+ public void setupAttributes(Collection<? extends Tree> trees) {
+ // nothing to do...
+ }
+
+ public void setDisplayAttribute(String displayAttribute) {
+ }
+
+ private BasicStroke scaleBarStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
+
+ private ScaleBarType type = ScaleBarType.BAR;
+ private ScaleBarAxis axis = new ScaleBarAxis(ScaleBarAxis.AT_DATA, ScaleBarAxis.AT_DATA);
+
+ private double scaleRange;
+ private double topMargin = 4.0;
+ private double bottomMargin = 4.0;
+ private double userScaleRange = 0.0;
+ private boolean automaticScale = true;
+
+ private double preferredHeight;
+ private double preferredWidth;
+
+ private float yOffset;
+
+ protected TreePane treePane;
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainterController.java b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainterController.java
new file mode 100644
index 0000000..199fd63
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/painters/ScaleBarPainterController.java
@@ -0,0 +1,219 @@
+package jebl.gui.trees.treeviewer_dev.painters;
+
+import org.virion.jam.components.RealNumberField;
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: ScaleBarPainterController.java 642 2007-02-16 19:35:15Z rambaut $
+ */
+public class ScaleBarPainterController extends AbstractController {
+
+ private static Preferences PREFS = Preferences.userNodeForPackage(ScaleBarPainterController.class);
+
+ private static final String SCALE_BAR_KEY = "scaleBar";
+
+ private static final String FONT_NAME_KEY = "fontName";
+ private static final String FONT_SIZE_KEY = "fontSize";
+ private static final String FONT_STYLE_KEY = "fontStyle";
+
+ private static final String NUMBER_FORMATTING_KEY = "numberFormatting";
+
+
+ private static final String AUTOMATIC_SCALE_KEY = "automaticScale";
+ private static final String SCALE_RANGE_KEY = "scaleRange";
+ private static final String LINE_WIDTH_KEY = "lineWidth";
+
+ private static final String SIGNIFICANT_DIGITS_KEY = "significantDigits";
+
+ // The defaults if there is nothing in the preferences
+ private static String DEFAULT_FONT_NAME = "sansserif";
+ private static int DEFAULT_FONT_SIZE = 6;
+ private static int DEFAULT_FONT_STYLE = Font.PLAIN;
+
+ private static String DEFAULT_NUMBER_FORMATTING = "#.####";
+ private static float DEFAULT_LINE_WIDTH = 1.0f;
+
+ public ScaleBarPainterController(final ScaleBarPainter scaleBarPainter) {
+ this.scaleBarPainter = scaleBarPainter;
+
+ final String defaultFontName = PREFS.get(FONT_NAME_KEY, DEFAULT_FONT_NAME);
+ final int defaultFontStyle = PREFS.getInt(FONT_SIZE_KEY, DEFAULT_FONT_STYLE);
+ final int defaultFontSize = PREFS.getInt(FONT_STYLE_KEY, DEFAULT_FONT_SIZE);
+ final String defaultNumberFormatting = PREFS.get(NUMBER_FORMATTING_KEY, DEFAULT_NUMBER_FORMATTING);
+
+ float lineWidth = PREFS.getFloat(LINE_WIDTH_KEY, DEFAULT_LINE_WIDTH);
+
+ scaleBarPainter.setFont(new Font(defaultFontName, defaultFontStyle, defaultFontSize));
+ scaleBarPainter.setNumberFormat(new DecimalFormat(defaultNumberFormatting));
+ scaleBarPainter.setScaleBarStroke(new BasicStroke(lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+
+ optionsPanel = new OptionsPanel();
+
+ titleCheckBox = new JCheckBox(getTitle());
+
+ titleCheckBox.setSelected(scaleBarPainter.isVisible());
+
+ autoScaleCheck = new JCheckBox("Automatic scale");
+ autoScaleCheck.setSelected(true);
+ optionsPanel.addComponent(autoScaleCheck, true);
+
+ scaleRangeText = new RealNumberField(0.0, Double.MAX_VALUE);
+ scaleRangeText.setValue(0.0);
+
+ final JLabel label1 = optionsPanel.addComponentWithLabel("Scale Range:", scaleRangeText, true);
+ label1.setEnabled(false);
+ scaleRangeText.setEnabled(false);
+
+ Font font = scaleBarPainter.getFont();
+ fontSizeSpinner = new JSpinner(new SpinnerNumberModel(font.getSize(), 0.01, 48, 1));
+
+ final JLabel label2 = optionsPanel.addComponentWithLabel("Font Size:", fontSizeSpinner);
+
+ fontSizeSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final float size = ((Double) fontSizeSpinner.getValue()).floatValue();
+ Font font = scaleBarPainter.getFont().deriveFont(size);
+ scaleBarPainter.setFont(font);
+ }
+ });
+
+ NumberFormat format = this.scaleBarPainter.getNumberFormat();
+ int digits = format.getMaximumFractionDigits();
+ digitsSpinner = new JSpinner(new SpinnerNumberModel(digits, 2, 14, 1));
+ final JLabel label3 = optionsPanel.addComponentWithLabel("Significant Digits:", digitsSpinner);
+
+ digitsSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final int digits = (Integer)digitsSpinner.getValue();
+ NumberFormat format = scaleBarPainter.getNumberFormat();
+ format.setMaximumFractionDigits(digits);
+ scaleBarPainter.setNumberFormat(format);
+ }
+ });
+
+ lineWeightSpinner = new JSpinner(new SpinnerNumberModel(1.0, 0.01, 48.0, 1.0));
+
+ lineWeightSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ float weight = ((Double) lineWeightSpinner.getValue()).floatValue();
+ scaleBarPainter.setScaleBarStroke(new BasicStroke(weight, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ }
+ });
+ final JLabel label4 = optionsPanel.addComponentWithLabel("Line Weight:", lineWeightSpinner);
+
+ final boolean isSelected1 = titleCheckBox.isSelected();
+ final boolean isSelected2 = autoScaleCheck.isSelected();
+ label1.setEnabled(isSelected1 && !isSelected2);
+ scaleRangeText.setEnabled(isSelected1 && !isSelected2);
+ label2.setEnabled(isSelected1);
+ fontSizeSpinner.setEnabled(isSelected1);
+ label3.setEnabled(isSelected1);
+ digitsSpinner.setEnabled(isSelected1);
+ label4.setEnabled(isSelected1);
+ lineWeightSpinner.setEnabled(isSelected1);
+
+ titleCheckBox.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final boolean isSelected1 = titleCheckBox.isSelected();
+ final boolean isSelected2 = autoScaleCheck.isSelected();
+
+ autoScaleCheck.setEnabled(isSelected1);
+ label1.setEnabled(isSelected1 && !isSelected2);
+ scaleRangeText.setEnabled(isSelected1 && !isSelected2);
+ label2.setEnabled(isSelected1);
+ fontSizeSpinner.setEnabled(isSelected1);
+ label3.setEnabled(isSelected1);
+ digitsSpinner.setEnabled(isSelected1);
+ label4.setEnabled(isSelected1);
+ lineWeightSpinner.setEnabled(isSelected1);
+
+ scaleBarPainter.setVisible(isSelected1);
+ }
+ });
+
+ autoScaleCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ if (autoScaleCheck.isSelected()) {
+ scaleBarPainter.setAutomaticScale(true);
+ double range = scaleBarPainter.getScaleRange();
+ scaleRangeText.setValue(range);
+ label1.setEnabled(false);
+ scaleRangeText.setEnabled(false);
+ } else {
+ label1.setEnabled(true);
+ scaleRangeText.setEnabled(true);
+ scaleBarPainter.setAutomaticScale(false);
+ }
+ }
+ });
+
+ scaleRangeText.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ Double value = scaleRangeText.getValue();
+ if (value != null) {
+ scaleBarPainter.setScaleRange(value);
+ }
+ }
+ });
+ }
+
+ public JComponent getTitleComponent() {
+ return titleCheckBox;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // force a toggle of the checkbox
+ autoScaleCheck.setSelected(false);
+ autoScaleCheck.setSelected(true);
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ autoScaleCheck.setSelected((Boolean)settings.get(SCALE_BAR_KEY + "." + AUTOMATIC_SCALE_KEY));
+ scaleRangeText.setValue((Double)settings.get(SCALE_BAR_KEY + "." + SCALE_RANGE_KEY));
+ fontSizeSpinner.setValue((Double)settings.get(SCALE_BAR_KEY + "." + FONT_SIZE_KEY));
+ digitsSpinner.setValue((Integer)settings.get(SCALE_BAR_KEY + "." + SIGNIFICANT_DIGITS_KEY));
+ lineWeightSpinner.setValue((Double)settings.get(SCALE_BAR_KEY + "." + LINE_WIDTH_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(SCALE_BAR_KEY + "." + AUTOMATIC_SCALE_KEY, autoScaleCheck.isSelected());
+ settings.put(SCALE_BAR_KEY + "." + SCALE_RANGE_KEY, scaleRangeText.getValue());
+ settings.put(SCALE_BAR_KEY + "." + FONT_SIZE_KEY, fontSizeSpinner.getValue());
+ settings.put(SCALE_BAR_KEY + "." + SIGNIFICANT_DIGITS_KEY, digitsSpinner.getValue());
+ settings.put(SCALE_BAR_KEY + "." + LINE_WIDTH_KEY, lineWeightSpinner.getValue());
+ }
+
+ private final JCheckBox titleCheckBox;
+ private final OptionsPanel optionsPanel;
+
+ private final JCheckBox autoScaleCheck;
+ private final RealNumberField scaleRangeText;
+ private final JSpinner fontSizeSpinner;
+ private final JSpinner digitsSpinner;
+ private final JSpinner lineWeightSpinner;
+
+ public String getTitle() {
+ return "Scale Bar";
+ }
+
+ private final ScaleBarPainter scaleBarPainter;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/AbstractTreeLayout.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/AbstractTreeLayout.java
new file mode 100644
index 0000000..c617e3a
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/AbstractTreeLayout.java
@@ -0,0 +1,71 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: AbstractTreeLayout.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public abstract class AbstractTreeLayout implements TreeLayout {
+ public void addTreeLayoutListener(TreeLayoutListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeTreeLayoutListener(TreeLayoutListener listener) {
+ listeners.remove(listener);
+ }
+
+ protected void fireTreeLayoutChanged() {
+ for (TreeLayoutListener listener : listeners) {
+ listener.treeLayoutChanged();
+ }
+ }
+
+ public String getBranchColouringAttributeName() {
+ return branchColouringAttribute;
+ }
+
+ public void setBranchColouringAttributeName(String branchColouringAttribute) {
+ this.branchColouringAttribute = branchColouringAttribute;
+ fireTreeLayoutChanged();
+ }
+
+ public boolean isShowingColouring() {
+ return branchColouringAttribute != null;
+ }
+
+ public String getCartoonAttributeName() {
+ return cartoonAttributeName;
+ }
+
+ public void setCartoonAttributeName(String cartoonAttributeName) {
+ this.cartoonAttributeName = cartoonAttributeName;
+ fireTreeLayoutChanged();
+ }
+
+ public boolean isShowingCartoonTipLabels() {
+ return showingCartoonTipLabels;
+ }
+
+ public void setShowingCartoonTipLabels(boolean showingCartoonTipLabels) {
+ this.showingCartoonTipLabels = showingCartoonTipLabels;
+ fireTreeLayoutChanged();
+ }
+
+ public String getCollapsedAttributeName() {
+ return collapsedAttributeName;
+ }
+
+ public void setCollapsedAttributeName(String collapsedAttributeName) {
+ this.collapsedAttributeName = collapsedAttributeName;
+ fireTreeLayoutChanged();
+ }
+
+ private Set<TreeLayoutListener> listeners = new HashSet<TreeLayoutListener>();
+ protected String branchColouringAttribute = null;
+ protected String cartoonAttributeName = null;
+ protected boolean showingCartoonTipLabels = true;
+
+ protected String collapsedAttributeName = null;
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayout.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayout.java
new file mode 100644
index 0000000..46e554a
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayout.java
@@ -0,0 +1,519 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PolarTreeLayout.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class PolarTreeLayout extends AbstractTreeLayout {
+
+ private double rootAngle = 180.0;
+ private double rootLength = 0.01;
+ private double angularRange = 360.0;
+
+ private boolean showingRootBranch = true;
+
+ private TipLabelPosition tipLabelPosition = TipLabelPosition.FLUSH;
+
+ public enum TipLabelPosition {
+ FLUSH,
+ RADIAL,
+ HORIZONTAL
+ }
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public boolean maintainAspectRatio() {
+ return true;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Shape getHeightLine(double height) {
+ return new Ellipse2D.Double(0.0, 0.0, height * 2.0, height * 2.0);
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ Area area1 = new Area(new Ellipse2D.Double(0.0, 0.0, height2 * 2.0, height2 * 2.0));
+ Area area2 = new Area(new Ellipse2D.Double(0.0, 0.0, height1 * 2.0, height1 * 2.0));
+ area1.subtract(area2);
+ return area1;
+ }
+
+
+ public double getRootAngle() {
+ return rootAngle;
+ }
+
+ public double getAngularRange() {
+ return angularRange;
+ }
+
+ public boolean isShowingRootBranch() {
+ return showingRootBranch;
+ }
+
+ public double getRootLength() {
+ return rootLength;
+ }
+
+ public TipLabelPosition getTipLabelPosition() {
+ return tipLabelPosition;
+ }
+
+ public void setRootAngle(double rootAngle) {
+ this.rootAngle = rootAngle;
+ fireTreeLayoutChanged();
+ }
+
+ public void setAngularRange(double angularRange) {
+ this.angularRange = angularRange;
+ fireTreeLayoutChanged();
+ }
+
+ public void setShowingRootBranch(boolean showingRootBranch) {
+ this.showingRootBranch = showingRootBranch;
+ fireTreeLayoutChanged();
+ }
+
+ public void setRootLength(double rootLength) {
+ this.rootLength = rootLength;
+ fireTreeLayoutChanged();
+ }
+
+ public void setTipLabelPosition(TipLabelPosition tipLabelPosition) {
+ this.tipLabelPosition = tipLabelPosition;
+ fireTreeLayoutChanged();
+ }
+
+ public void layout(RootedTree tree, TreeLayoutCache cache) {
+
+ cache.clear();
+
+ Node root = tree.getRootNode();
+ double rl = (rootLength * tree.getHeight(root)) * 10.0;
+
+ maxXPosition = 0.0;
+ getMaxXPosition(tree, root, rl);
+
+ yPosition = 0.0;
+ yIncrement = 1.0 / tree.getExternalNodes().size();
+
+ final Point2D rootPoint = constructNode(tree, root, rl, cache);
+
+ if (showingRootBranch) {
+ // construct a root branch line
+ final double y = rootPoint.getY();
+ Line2D line = new Line2D.Double(transform(0.0, y), transform(rootPoint.getX(), y));
+
+ // add the line to the map of branch paths
+ cache.branchPaths.put(root, line);
+ }
+ }
+
+ private Point2D constructNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ if (!tree.isExternal(node)) {
+
+ if (collapsedAttributeName != null && node.getAttribute(collapsedAttributeName) != null) {
+ nodePoint = constructCollapsedNode(tree, node, xPosition, cache);
+ } else if (cartoonAttributeName != null && node.getAttribute(cartoonAttributeName) != null) {
+ nodePoint = constructCartoonNode(tree, node, xPosition, cache);
+ } else {
+ double yPos = 0.0;
+
+ List<Node> children = tree.getChildren(node);
+ Point2D[] childPoints = new Point2D[children.size()];
+
+ int i = 0;
+ for (Node child : children) {
+
+ final double length = tree.getLength(child);
+ childPoints[i] = constructNode(tree, child, xPosition + length, cache);
+ yPos += childPoints[i].getY();
+
+ i++;
+ }
+
+ // the y-position of the node is the average of the child nodes
+ yPos /= children.size();
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+ Point2D transformedNodePoint = transform(nodePoint);
+
+ final double start = getAngle(yPos);
+
+ i = 0;
+ for (Node child : children) {
+
+ GeneralPath branchPath = new GeneralPath();
+ final Point2D transformedChildPoint = transform(childPoints[i]);
+
+ final Point2D transformedShoulderPoint = transform(
+ nodePoint.getX(), childPoints[i].getY());
+
+ Object[] colouring = null;
+ if (branchColouringAttribute != null) {
+ colouring = (Object[])child.getAttribute(branchColouringAttribute);
+ }
+ if (colouring != null) {
+ // If there is a colouring, then we break the path up into
+ // segments. This should allow use to iterate along the segments
+ // and colour them as we draw them.
+
+ float nodeHeight = (float) tree.getHeight(node);
+ float childHeight = (float) tree.getHeight(child);
+
+ double x1 = childPoints[i].getX();
+ double x0 = nodePoint.getX();
+
+ branchPath.moveTo(
+ (float) transformedChildPoint.getX(),
+ (float) transformedChildPoint.getY());
+
+ for (int j = 0; j < colouring.length - 1; j+=2) {
+ double height = ((Number)colouring[j+1]).doubleValue();
+ double p = (height - childHeight) / (nodeHeight - childHeight);
+ double x = x1 - ((x1 - x0) * p);
+ final Point2D transformedPoint = transform(x, childPoints[i].getY());
+ branchPath.lineTo(
+ (float) transformedPoint.getX(),
+ (float) transformedPoint.getY());
+ }
+ branchPath.lineTo(
+ (float) transformedShoulderPoint.getX(),
+ (float) transformedShoulderPoint.getY());
+ } else {
+ branchPath.moveTo(
+ (float) transformedChildPoint.getX(),
+ (float) transformedChildPoint.getY());
+
+ branchPath.lineTo(
+ (float) transformedShoulderPoint.getX(),
+ (float) transformedShoulderPoint.getY());
+ }
+
+ final double finish = getAngle(childPoints[i].getY());
+
+ Arc2D arc = new Arc2D.Double();
+ arc.setArcByCenter(0.0, 0.0, nodePoint.getX(), finish, start - finish, Arc2D.OPEN);
+ branchPath.append(arc, true);
+
+ // add the branchPath to the map of branch paths
+ cache.branchPaths.put(child, branchPath);
+
+ final double x3 = (nodePoint.getX() + childPoints[i].getX()) / 2;
+
+ Line2D branchLabelPath = new Line2D.Double(
+ transform(x3 - 1.0, childPoints[i].getY()),
+ transform(x3 + 1.0, childPoints[i].getY()));
+
+ cache.branchLabelPaths.put(child, branchLabelPath);
+
+ i++;
+ }
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() + 1.0, yPos));
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() - 1.0, yPos));
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, transformedNodePoint);
+ }
+ } else {
+
+ nodePoint = new Point2D.Double(xPosition, yPosition);
+ Point2D transformedNodePoint = transform(nodePoint);
+
+ Line2D tipLabelPath;
+
+ if (tipLabelPosition == TipLabelPosition.FLUSH) {
+
+ tipLabelPath = new Line2D.Double(transformedNodePoint, transform(xPosition + 1.0, yPosition));
+
+ } else if (tipLabelPosition == TipLabelPosition.RADIAL) {
+
+ tipLabelPath = new Line2D.Double(transform(maxXPosition, yPosition),
+ transform(maxXPosition + 1.0, yPosition));
+
+ Line2D calloutPath = new Line2D.Double(transformedNodePoint, transform(maxXPosition, yPosition));
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else if (tipLabelPosition == TipLabelPosition.HORIZONTAL) {
+ // this option disabled in getControls (JH)
+ throw new UnsupportedOperationException("Not implemented yet");
+ } else {
+ // this is a bug
+ throw new IllegalArgumentException("Unrecognized enum value");
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ yPosition += yIncrement;
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, transformedNodePoint);
+ }
+
+ return nodePoint;
+ }
+
+ private Point2D constructCartoonNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ Object[] values = (Object[])node.getAttribute(cartoonAttributeName);
+ int tipCount = (Integer)values[0];
+ double tipHeight = (Double)values[1];
+ double height = tree.getHeight(node);
+ double maxXPos = xPosition + height - tipHeight;
+
+ double minYPos = yPosition;
+ yPosition += yIncrement * (tipCount - 1);
+ double maxYPos = yPosition;
+ yPosition += yIncrement;
+
+ // the y-position of the node is the average of the child nodes
+ double yPos = (maxYPos + minYPos) / 2;
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+ Point2D transformedNodePoint0 = transform(nodePoint);
+ Point2D transformedNodePoint1 = transform(new Point2D.Double(maxXPos, minYPos));
+ Point2D transformedNodePoint2 = transform(new Point2D.Double(maxXPos, maxYPos));
+
+ GeneralPath collapsedShape = new GeneralPath();
+
+ collapsedShape.moveTo((float)transformedNodePoint0.getX(), (float)transformedNodePoint0.getY());
+ collapsedShape.lineTo((float)transformedNodePoint1.getX(), (float)transformedNodePoint1.getY());
+ final double start = getAngle(maxYPos);
+ final double finish = getAngle(minYPos);
+ Arc2D arc = new Arc2D.Double();
+ arc.setArcByCenter(0.0, 0.0, maxXPos, finish, start - finish, Arc2D.OPEN);
+ collapsedShape.append(arc, true);
+ collapsedShape.closePath();
+
+ // add the collapsedShape to the map of branch paths
+ cache.collapsedShapes.put(node, collapsedShape);
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() + 1.0, yPos));
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() - 1.0, yPos));
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+
+ if (showingCartoonTipLabels) {
+ constructCartoonTipLabelPaths(tree, node, maxXPos, new double[] { minYPos }, cache);
+ }
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, transformedNodePoint0);
+
+ return nodePoint;
+ }
+
+ private void constructCartoonTipLabelPaths(RootedTree tree, Node node, double xPosition, double[] yPosition, TreeLayoutCache cache) {
+
+ if (!tree.isExternal(node)) {
+ for (Node child : tree.getChildren(node)) {
+ constructCartoonTipLabelPaths(tree, child, xPosition, yPosition, cache);
+ }
+ } else {
+
+ Point2D nodePoint = new Point2D.Double(xPosition, yPosition[0]);
+
+ Point2D transformedNodePoint = transform(nodePoint);
+
+ Line2D tipLabelPath;
+
+ if (tipLabelPosition == TipLabelPosition.FLUSH) {
+
+ tipLabelPath = new Line2D.Double(transformedNodePoint, transform(xPosition + 1.0, yPosition[0]));
+
+ } else if (tipLabelPosition == TipLabelPosition.RADIAL) {
+
+ tipLabelPath = new Line2D.Double(transform(maxXPosition, yPosition[0]),
+ transform(maxXPosition + 1.0, yPosition[0]));
+
+ Line2D calloutPath = new Line2D.Double(transformedNodePoint, transform(maxXPosition, yPosition[0]));
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else if (tipLabelPosition == TipLabelPosition.HORIZONTAL) {
+ // this option disabled in getControls (JH)
+ throw new UnsupportedOperationException("Not implemented yet");
+ } else {
+ // this is a bug
+ throw new IllegalArgumentException("Unrecognized enum value");
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ yPosition[0] += yIncrement;
+
+ }
+ }
+
+ private Point2D constructCollapsedNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ Object[] values = (Object[])node.getAttribute(collapsedAttributeName);
+ String tipName = (String)values[0];
+ double tipHeight = (Double)values[1];
+ double height = tree.getHeight(node);
+ double maxXPos = xPosition + height - tipHeight;
+
+ double minYPos = yPosition - (yIncrement * 0.5);
+ double maxYPos = minYPos + yIncrement;
+ yPosition += yIncrement;
+
+ // the y-position of the node is the average of the child nodes
+ double yPos = (maxYPos + minYPos) / 2;
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+ Point2D transformedNodePoint0 = transform(nodePoint);
+ Point2D transformedNodePoint1 = transform(new Point2D.Double(maxXPos, minYPos));
+
+ GeneralPath collapsedShape = new GeneralPath();
+
+ collapsedShape.moveTo((float)transformedNodePoint0.getX(), (float)transformedNodePoint0.getY());
+
+ collapsedShape.lineTo((float)transformedNodePoint1.getX(), (float)transformedNodePoint1.getY());
+ final double start = getAngle(maxYPos);
+ final double finish = getAngle(minYPos);
+ Arc2D arc = new Arc2D.Double();
+ arc.setArcByCenter(0.0, 0.0, maxXPos, finish, start - finish, Arc2D.OPEN);
+ collapsedShape.append(arc, true);
+ collapsedShape.closePath();
+
+ // add the collapsedShape to the map of branch paths
+ cache.collapsedShapes.put(node, collapsedShape);
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() + 1.0, yPos));
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ transform(nodePoint.getX(), yPos),
+ transform(nodePoint.getX() - 1.0, yPos));
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+
+ Point2D transformedNodePoint = transform(maxXPos, yPos);
+
+ Line2D tipLabelPath;
+
+ if (tipLabelPosition == TipLabelPosition.FLUSH) {
+
+ tipLabelPath = new Line2D.Double(transformedNodePoint, transform(maxXPos + 1.0, yPos));
+
+ } else if (tipLabelPosition == TipLabelPosition.RADIAL) {
+
+ tipLabelPath = new Line2D.Double(transform(maxXPosition, yPos),
+ transform(maxXPosition + 1.0, yPos));
+
+ Line2D calloutPath = new Line2D.Double(transformedNodePoint, transform(maxXPosition, yPos));
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else if (tipLabelPosition == TipLabelPosition.HORIZONTAL) {
+ // this option disabled in getControls (JH)
+ throw new UnsupportedOperationException("Not implemented yet");
+ } else {
+ // this is a bug
+ throw new IllegalArgumentException("Unrecognized enum value");
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, transformedNodePoint0);
+
+ return nodePoint;
+ }
+
+ private void getMaxXPosition(RootedTree tree, Node node, double xPosition) {
+
+ if (!tree.isExternal(node)) {
+
+ List<Node> children = tree.getChildren(node);
+
+ for (Node child : children) {
+ final double length = tree.getLength(child);
+ getMaxXPosition(tree, child, xPosition + length);
+ }
+
+ } else {
+ if (xPosition > maxXPosition) {
+ maxXPosition = xPosition;
+ }
+ }
+ }
+
+ /**
+ * Polar transform
+ *
+ * @param point
+ * @return the point in polar space
+ */
+ private Point2D transform(Point2D point) {
+ return transform(point.getX(), point.getY());
+ }
+
+ /**
+ * Polar transform
+ *
+ * @param x
+ * @param y
+ * @return the point in polar space
+ */
+ private Point2D transform(double x, double y) {
+ double r = - Math.toRadians(getAngle(y));
+ double tx = x * Math.cos(r);
+ double ty = x * Math.sin(r);
+ return new Point2D.Double(tx, ty);
+ }
+
+ private double getAngle(double y) {
+ return rootAngle - ((360.0 - angularRange) * 0.5) - (y * angularRange);
+ }
+
+ private double yPosition;
+ private double yIncrement;
+
+ private double maxXPosition;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayoutController.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayoutController.java
new file mode 100644
index 0000000..3c3e5a9
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/PolarTreeLayoutController.java
@@ -0,0 +1,157 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PolarTreeLayoutController.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class PolarTreeLayoutController extends AbstractController {
+
+ private static final String POLAR_LAYOUT_KEY = "polarLayout";
+
+ private static final String ROOT_ANGLE_KEY = "rootAngle";
+ private static final String ANGULAR_RANGE_KEY = "angularRange";
+ private static final String ROOT_LENGTH_KEY = "rootLength";
+ private static final String SHOW_ROOT_KEY = "showRoot";
+ private static final String ALIGN_TIP_LABELS_KEY = "alignTipLabels";
+
+ public PolarTreeLayoutController(final PolarTreeLayout treeLayout) {
+ this.treeLayout = treeLayout;
+
+ titleLabel = new JLabel("Polar Layout");
+ optionsPanel = new OptionsPanel(4, 6);
+
+ rootAngleSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 360000, 0);
+ rootAngleSlider.setOpaque(false);
+ rootAngleSlider.setValue((int) ((180.0 - (treeLayout.getRootAngle()) * 1000)));
+ //rootAngleSlider.setMajorTickSpacing(rootAngleSlider.getMaximum() / 5);
+// rootAngleSlider.setPaintTicks(true);
+
+ rootAngleSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = 180 + (rootAngleSlider.getValue() / 1000.0);
+ treeLayout.setRootAngle(value % 360);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Angle:", rootAngleSlider, true);
+
+ angularRangeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 360000, 0);
+ angularRangeSlider.setOpaque(false);
+ angularRangeSlider.setValue((int) ((360.0 - (treeLayout.getAngularRange()) * 1000.0)));
+ //angularRangeSlider.setMajorTickSpacing(angularRangeSlider.getMaximum() / 5);
+// angularRangeSlider.setPaintTicks(true);
+
+ angularRangeSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = 360.0 - (angularRangeSlider.getValue() / 1000.0);
+ treeLayout.setAngularRange(value);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Angle Range:", angularRangeSlider, true);
+
+ final int sliderMax = 10000;
+ rootLengthSlider = new JSlider(SwingConstants.HORIZONTAL, 0, sliderMax, 0);
+ rootLengthSlider.setOpaque(false);
+ rootLengthSlider.setValue((int) (treeLayout.getRootLength() * sliderMax));
+ //rootLengthSlider.setMajorTickSpacing(rootLengthSlider.getMaximum() / 5);
+ // rootLengthSlider.setPaintTicks(true);
+
+ rootLengthSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = rootLengthSlider.getValue();
+ treeLayout.setRootLength(value / sliderMax);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Length:", rootLengthSlider, true);
+
+ showRootCheck = new JCheckBox("Show Root");
+ showRootCheck.setOpaque(false);
+ optionsPanel.addComponent(showRootCheck);
+
+ showRootCheck.setSelected(treeLayout.isShowingRootBranch());
+ showRootCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ treeLayout.setShowingRootBranch(showRootCheck.isSelected());
+ }
+ });
+
+
+// labelPositionCombo = new JComboBox();
+// for (PolarTreeLayout.TipLabelPosition position : PolarTreeLayout.TipLabelPosition.values()) {
+// if (position != PolarTreeLayout.TipLabelPosition.HORIZONTAL) // not implemented yet
+// labelPositionCombo.addItem(position);
+// }
+// labelPositionCombo.setSelectedItem(treeLayout.getTipLabelPosition());
+// labelPositionCombo.addItemListener(new ItemListener() {
+// public void itemStateChanged(ItemEvent itemEvent) {
+// treeLayout.setTipLabelPosition((PolarTreeLayout.TipLabelPosition) labelPositionCombo.getSelectedItem());
+//
+// }
+// });
+// optionsPanel.addComponentWithLabel("Label Position:", labelPositionCombo);
+
+ alignTipLabelsCheck = new JCheckBox("Align Tip Labels");
+ alignTipLabelsCheck.setOpaque(false);
+
+ alignTipLabelsCheck.setSelected(treeLayout.getTipLabelPosition() == PolarTreeLayout.TipLabelPosition.RADIAL);
+ alignTipLabelsCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ treeLayout.setTipLabelPosition(alignTipLabelsCheck.isSelected() ? PolarTreeLayout.TipLabelPosition.RADIAL : PolarTreeLayout.TipLabelPosition.FLUSH);
+ }
+ });
+ optionsPanel.addComponent(alignTipLabelsCheck);
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ rootAngleSlider.setValue((Integer) settings.get(POLAR_LAYOUT_KEY + "." + ROOT_ANGLE_KEY));
+ angularRangeSlider.setValue((Integer) settings.get(POLAR_LAYOUT_KEY + "." + ANGULAR_RANGE_KEY));
+ rootLengthSlider.setValue((Integer) settings.get(POLAR_LAYOUT_KEY + "." + ROOT_LENGTH_KEY));
+ showRootCheck.setSelected((Boolean) settings.get(POLAR_LAYOUT_KEY + "." + SHOW_ROOT_KEY));
+ alignTipLabelsCheck.setSelected((Boolean) settings.get(POLAR_LAYOUT_KEY + "." + ALIGN_TIP_LABELS_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(POLAR_LAYOUT_KEY + "." + ROOT_ANGLE_KEY, rootAngleSlider.getValue());
+ settings.put(POLAR_LAYOUT_KEY + "." + ANGULAR_RANGE_KEY, angularRangeSlider.getValue());
+ settings.put(POLAR_LAYOUT_KEY + "." + ROOT_LENGTH_KEY, rootLengthSlider.getValue());
+ settings.put(POLAR_LAYOUT_KEY + "." + SHOW_ROOT_KEY, showRootCheck.isSelected());
+ settings.put(POLAR_LAYOUT_KEY + "." + ALIGN_TIP_LABELS_KEY, alignTipLabelsCheck.isSelected());
+ }
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final JSlider rootAngleSlider;
+ private final JSlider rootLengthSlider;
+ private final JSlider angularRangeSlider;
+ //private final JComboBox labelPositionCombo;
+ private final JCheckBox alignTipLabelsCheck;
+
+ private final JCheckBox showRootCheck;
+
+ private final PolarTreeLayout treeLayout;
+
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayout.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayout.java
new file mode 100644
index 0000000..b8959b8
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayout.java
@@ -0,0 +1,187 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import jebl.evolution.graphs.Graph;
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RadialTreeLayout.java 535 2006-11-21 11:11:20Z rambaut $
+ */
+public class RadialTreeLayout extends AbstractTreeLayout {
+
+ private double spread = 0.0;
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public boolean isShowingRootBranch() {
+ return false;
+ }
+
+ public boolean maintainAspectRatio() {
+ return true;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
+ }
+
+ public Line2D getHeightLine(double height) {
+ throw new UnsupportedOperationException("Method getHeightLine() is not supported in this TreeLayout");
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ throw new UnsupportedOperationException("Method getHeightArea() is not supported in this TreeLayout");
+ }
+
+ public double getSpread() {
+ return spread;
+ }
+
+ public void setSpread(double spread) {
+ this.spread = spread;
+ fireTreeLayoutChanged();
+ }
+
+ public void layout(RootedTree tree, TreeLayoutCache cache) {
+ cache.clear();
+
+ try {
+ final Node root = tree.getRootNode();
+
+ constructNode(tree, root, 0.0, Math.PI * 2, 0.0, 0.0, 0.0, cache);
+
+ } catch (Graph.NoEdgeException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Point2D constructNode(RootedTree tree, Node node,
+ double angleStart, double angleFinish, double xPosition,
+ double yPosition, double length,
+ TreeLayoutCache cache) throws Graph.NoEdgeException {
+
+ final double branchAngle = (angleStart + angleFinish) / 2.0;
+
+ final double directionX = Math.cos(branchAngle);
+ final double directionY = Math.sin(branchAngle);
+ Point2D nodePoint = new Point2D.Double(xPosition + (length * directionX), yPosition + (length * directionY));
+
+ // System.out.println("Node: " + Utils.DEBUGsubTreeRep(tree, node) + " at " + nodePoint);
+
+ if (!tree.isExternal(node)) {
+
+ List<Node> children = tree.getChildren(node);
+ int[] leafCounts = new int[children.size()];
+ int sumLeafCount = 0;
+
+ int i = 0;
+ for (Node child : children) {
+ leafCounts[i] = jebl.evolution.trees.Utils.getExternalNodeCount(tree, child);
+ sumLeafCount += leafCounts[i];
+ i++;
+ }
+
+ double span = (angleFinish - angleStart);
+
+ if (!tree.isRoot(node)) {
+ span *= 1.0 + (spread / 10.0);
+ angleStart = branchAngle - (span / 2.0);
+ angleFinish = branchAngle + (span / 2.0);
+ }
+
+ double a2 = angleStart;
+
+ i = 0;
+ for (Node child : children) {
+
+ final double childLength = tree.getLength(child);
+ double a1 = a2;
+ a2 = a1 + (span * leafCounts[i] / sumLeafCount);
+
+ Point2D childPoint = constructNode(tree, child, a1, a2,
+ nodePoint.getX(), nodePoint.getY(), childLength, cache);
+
+ Line2D branchLine = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ childPoint.getX(), childPoint.getY());
+
+ Object[] colouring = null;
+ if (branchColouringAttribute != null) {
+ colouring = (Object[])child.getAttribute(branchColouringAttribute);
+ }
+ if (colouring != null) {
+ // If there is a colouring, then we break the path up into
+ // segments. This should allow use to iterate along the segments
+ // and colour them as we draw them.
+
+ float nodeHeight = (float) tree.getHeight(node);
+ float childHeight = (float) tree.getHeight(child);
+
+ float x1 = (float)childPoint.getX();
+ float y1 = (float)childPoint.getY();
+ float x0 = (float)nodePoint.getX();
+ float y0 = (float)nodePoint.getY();
+
+ GeneralPath branchPath = new GeneralPath();
+
+ // to help this, we are going to draw the branch backwards
+ branchPath.moveTo(x1, y1);
+ for (int j = 0; j < colouring.length - 1; j+=2) {
+ float height = ((Number)colouring[j+1]).floatValue();
+ float p = (height - childHeight) / (nodeHeight - childHeight);
+ float x = x1 + ((x0 - x1) * p);
+ float y = y1 + ((y0 - y1) * p);
+ branchPath.lineTo(x, y);
+ }
+ branchPath.lineTo(x0, y0);
+
+ // add the branchPath to the map of branch paths
+ cache.branchPaths.put(child, branchPath);
+
+ } else {
+ // add the branchLine to the map of branch paths
+ cache.branchPaths.put(child, branchLine);
+ }
+
+ cache.branchLabelPaths.put(child, (Line2D)branchLine.clone());
+ i++;
+ }
+
+ Point2D nodeLabelPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
+ yPosition + ((length + 1.0) * directionY));
+
+ Line2D nodeLabelPath = new Line2D.Double(nodePoint, nodeLabelPoint);
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Point2D nodeBarPoint = new Point2D.Double(xPosition + ((length - 1.0) * directionX),
+ yPosition + ((length - 1.0) * directionY));
+ Line2D nodeBarPath = new Line2D.Double(nodePoint, nodeBarPoint);
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+ } else {
+
+ Point2D taxonPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
+ yPosition + ((length + 1.0) * directionY));
+
+ Line2D taxonLabelPath = new Line2D.Double(nodePoint, taxonPoint);
+ cache.tipLabelPaths.put(node, taxonLabelPath);
+ }
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, nodePoint);
+
+ return nodePoint;
+ }
+
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayoutController.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayoutController.java
new file mode 100644
index 0000000..b817295
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RadialTreeLayoutController.java
@@ -0,0 +1,83 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RadialTreeLayoutController.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class RadialTreeLayoutController extends AbstractController {
+
+ private static final String RADIAL_LAYOUT_KEY = "radialLayout";
+
+ private static final String SPREAD_KEY = "spread";
+
+ public RadialTreeLayoutController(final RadialTreeLayout treeLayout) {
+ this.treeLayout = treeLayout;
+
+ titleLabel = new JLabel("Radial Layout");
+ optionsPanel = new OptionsPanel();
+
+// final int sliderMax = 100;
+// final JSlider spreadSlider = new JSlider(SwingConstants.HORIZONTAL, 0, sliderMax, 0);
+// spreadSlider.setValue((int)(treeLayout.getSpread() * sliderMax / 2.0));
+//
+// spreadSlider.addChangeListener(new ChangeListener() {
+// public void stateChanged(ChangeEvent changeEvent) {
+// double value = spreadSlider.getValue();
+// treeLayout.setSpread((value / sliderMax));
+// }
+// });
+// optionsPanel.addComponentWithLabel("Spread:", spreadSlider, true);
+
+ double spread = treeLayout.getSpread();
+ spreadSpinner = new JSpinner(new SpinnerNumberModel(spread, 0, 100, 1));
+
+ optionsPanel.addComponentWithLabel("Spread:", spreadSpinner, true);
+
+ spreadSpinner.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ final double spread = (Double)spreadSpinner.getValue();
+ treeLayout.setSpread(spread / 100.0);
+ }
+ });
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ spreadSpinner.setValue((Double) settings.get(RADIAL_LAYOUT_KEY + "." + SPREAD_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(RADIAL_LAYOUT_KEY + "." + SPREAD_KEY, spreadSpinner.getValue());
+ }
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final JSpinner spreadSpinner;
+
+ private final RadialTreeLayout treeLayout;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayout.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayout.java
new file mode 100644
index 0000000..ee1600d
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayout.java
@@ -0,0 +1,455 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import jebl.evolution.graphs.Node;
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RectilinearTreeLayout.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class RectilinearTreeLayout extends AbstractTreeLayout {
+
+ private double curvature = 0.0;
+ private double rootLength = 0.01;
+ private boolean alignTipLabels = false;
+
+ public AxisType getXAxisType() {
+ return AxisType.CONTINUOUS;
+ }
+
+ public AxisType getYAxisType() {
+ return AxisType.DISCRETE;
+ }
+
+ public boolean isShowingRootBranch() {
+ return true;
+ }
+
+ public boolean maintainAspectRatio() {
+ return false;
+ }
+
+ public double getHeightOfPoint(Point2D point) {
+ return point.getX();
+ }
+
+ public Shape getHeightLine(double height) {
+ double x = height;
+ double y1 = 0.0;
+ double y2 = 1.0;
+ return new Line2D.Double(x, y1, x, y2);
+ }
+
+ public Shape getHeightArea(double height1, double height2) {
+ double x = height1;
+ double y = 0.0;
+ double w = height2 - height1;
+ double h = 1.0;
+ return new Rectangle2D.Double(x, y, w, h);
+ }
+
+ public boolean isAlignTipLabels() {
+ return alignTipLabels;
+ }
+
+ public double getRootLength() {
+ return rootLength;
+ }
+
+ public double getCurvature() {
+ return curvature;
+ }
+
+ public void setAlignTipLabels(boolean alignTipLabels) {
+ this.alignTipLabels = alignTipLabels;
+ fireTreeLayoutChanged();
+ }
+
+ public void setCurvature(double curvature) {
+ this.curvature = curvature;
+ fireTreeLayoutChanged();
+ }
+
+ public void setRootLength(double rootLength) {
+ this.rootLength = rootLength;
+ fireTreeLayoutChanged();
+ }
+
+ public void layout(RootedTree tree, TreeLayoutCache cache) {
+
+ cache.clear();
+
+ maxXPosition = 0.0;
+
+ yPosition = 0.0;
+ yIncrement = 1.0 / (tree.getExternalNodes().size() + 1);
+
+ Node root = tree.getRootNode();
+ double rl = rootLength * tree.getHeight(root);
+
+ maxXPosition = 0.0;
+ getMaxXPosition(tree, root, rl);
+
+ Point2D rootPoint = constructNode(tree, root, rl, cache);
+
+ // construct a root branch line
+ Line2D line = new Line2D.Double(0.0, rootPoint.getY(), rootPoint.getX(), rootPoint.getY());
+
+ // add the line to the map of branch paths
+ cache.branchPaths.put(root, line);
+
+ }
+
+ private Point2D constructNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ if (!tree.isExternal(node)) {
+
+ if (collapsedAttributeName != null && node.getAttribute(collapsedAttributeName) != null) {
+ nodePoint = constructCollapsedNode(tree, node, xPosition, cache);
+ } else if (cartoonAttributeName != null && node.getAttribute(cartoonAttributeName) != null) {
+ nodePoint = constructCartoonNode(tree, node, xPosition, cache);
+ } else {
+ double yPos = 0.0;
+
+ List<Node> children = tree.getChildren(node);
+ for (Node child : children) {
+
+ double length = tree.getLength(child);
+ Point2D childPoint = constructNode(tree, child, xPosition + length, cache);
+ yPos += childPoint.getY();
+ }
+
+ // the y-position of the node is the average of the child nodes
+ yPos /= children.size();
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+
+ for (Node child : children) {
+
+ Point2D childPoint = cache.nodePoints.get(child);
+
+ GeneralPath branchPath = new GeneralPath();
+
+ // start point
+ float x0 = (float) nodePoint.getX();
+ float y0 = (float) nodePoint.getY();
+
+ // end point
+ float x1 = (float) childPoint.getX();
+ float y1 = (float) childPoint.getY();
+
+ if (curvature == 0.0) {
+ Object[] colouring = null;
+ if (branchColouringAttribute != null) {
+ colouring = (Object[])child.getAttribute(branchColouringAttribute);
+ }
+ if (colouring != null) {
+ // If there is a colouring, then we break the path up into
+ // segments. This should allow use to iterate along the segments
+ // and colour them as we draw them.
+
+
+ float nodeHeight = (float) tree.getHeight(node);
+ float childHeight = (float) tree.getHeight(child);
+
+ // to help this, we are going to draw the branch backwards
+ branchPath.moveTo(x1, y1);
+ for (int i = 0; i < colouring.length - 1; i+=2) {
+ float height = ((Number)colouring[i+1]).floatValue();
+ float p = (height - childHeight) / (nodeHeight - childHeight);
+ float x = x1 - ((x1 - x0) * p);
+ branchPath.lineTo(x, y1);
+ }
+ branchPath.lineTo(x0, y1);
+ branchPath.lineTo(x0, y0);
+ } else {
+ branchPath.moveTo(x0, y0);
+ branchPath.lineTo(x0, y1);
+ branchPath.lineTo(x1, y1);
+ }
+ } else if (curvature == 1.0) {
+ // The extreme is to use a triangular look
+ branchPath.moveTo(x0, y0);
+ branchPath.lineTo(x1, y1);
+ } else {
+ // if the curvature is on then we simply don't
+ // do tree colouring - I just can't be bothered to
+ // implement it (and it would probably be confusing anyway).
+ float x2 = x1 - ((x1 - x0) * (float) (1.0 - curvature));
+ float y2 = y0 + ((y1 - y0) * (float) (1.0 - curvature));
+
+ branchPath.moveTo(x0, y0);
+ branchPath.lineTo(x0, y2);
+ branchPath.quadTo(x0, y1, x2, y1);
+ branchPath.lineTo(x1, y1);
+ }
+
+
+ // add the branchPath to the map of branch paths
+ cache.branchPaths.put(child, branchPath);
+
+ double x3 = (nodePoint.getX() + childPoint.getX()) / 2;
+ Line2D branchLabelPath = new Line2D.Double(
+ x3 - 1.0, childPoint.getY(),
+ x3 + 1.0, childPoint.getY());
+
+ cache.branchLabelPaths.put(child, branchLabelPath);
+ }
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() + 1.0, nodePoint.getY());
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() - 1.0, nodePoint.getY());
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+ }
+ } else {
+
+ nodePoint = new Point2D.Double(xPosition, yPosition);
+
+ Line2D tipLabelPath;
+
+ if (alignTipLabels) {
+
+ tipLabelPath = new Line2D.Double(
+ maxXPosition, nodePoint.getY(),
+ maxXPosition + 1.0, nodePoint.getY());
+
+ Line2D calloutPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ maxXPosition, nodePoint.getY());
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else {
+ tipLabelPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() + 1.0, nodePoint.getY());
+
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ yPosition += yIncrement;
+
+ }
+
+ // add the node point to the map of node points
+ cache.nodePoints.put(node, nodePoint);
+
+ return nodePoint;
+ }
+
+ private Point2D constructCartoonNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ Object[] values = (Object[])node.getAttribute(cartoonAttributeName);
+ int tipCount = (Integer)values[0];
+ double tipHeight = (Double)values[1];
+ double height = tree.getHeight(node);
+ double maxXPos = xPosition + height - tipHeight;
+
+ double minYPos = yPosition;
+ yPosition += yIncrement * (tipCount - 1);
+ double maxYPos = yPosition;
+ yPosition += yIncrement;
+
+ // the y-position of the node is the average of the child nodes
+ double yPos = (maxYPos + minYPos) / 2;
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+
+ GeneralPath collapsedShape = new GeneralPath();
+
+ // start point
+ float x0 = (float)nodePoint.getX();
+ float y0 = (float)nodePoint.getY();
+
+ // end point
+ float x1 = (float)maxXPos;
+ float y1 = (float)minYPos;
+
+ float y2 = (float)maxYPos;
+
+ collapsedShape.moveTo(x0, y0);
+ collapsedShape.lineTo(x1, y1);
+ collapsedShape.lineTo(x1, y2);
+ collapsedShape.closePath();
+
+ // add the collapsedShape to the map of branch paths
+ cache.collapsedShapes.put(node, collapsedShape);
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() + 1.0, nodePoint.getY());
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() - 1.0, nodePoint.getY());
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+
+ if (showingCartoonTipLabels) {
+ constructCartoonTipLabelPaths(tree, node, maxXPos, new double[] { minYPos }, cache);
+ }
+
+ return nodePoint;
+ }
+
+ private void constructCartoonTipLabelPaths(RootedTree tree, Node node,
+ double xPosition, double[] yPosition,
+ TreeLayoutCache cache) {
+
+ if (!tree.isExternal(node)) {
+ for (Node child : tree.getChildren(node)) {
+ constructCartoonTipLabelPaths(tree, child, xPosition, yPosition, cache);
+ }
+ } else {
+
+ Point2D nodePoint = new Point2D.Double(xPosition, yPosition[0]);
+
+ Line2D tipLabelPath;
+
+ if (alignTipLabels) {
+
+ tipLabelPath = new Line2D.Double(
+ maxXPosition, nodePoint.getY(),
+ maxXPosition + 1.0, nodePoint.getY());
+
+ Line2D calloutPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ maxXPosition, nodePoint.getY());
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else {
+ tipLabelPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() + 1.0, nodePoint.getY());
+
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ yPosition[0] += yIncrement;
+
+ }
+ }
+
+ private Point2D constructCollapsedNode(RootedTree tree, Node node, double xPosition, TreeLayoutCache cache) {
+
+ Point2D nodePoint;
+
+ Object[] values = (Object[])node.getAttribute(collapsedAttributeName);
+ String tipName = (String)values[0];
+ double tipHeight = (Double)values[1];
+ double height = tree.getHeight(node);
+ double maxXPos = xPosition + height - tipHeight;
+
+ double minYPos = yPosition - (yIncrement * 0.5);
+ double maxYPos = minYPos + yIncrement;
+ yPosition += yIncrement;
+
+ // the y-position of the node is the average of the child nodes
+ double yPos = (maxYPos + minYPos) / 2;
+
+ nodePoint = new Point2D.Double(xPosition, yPos);
+
+ GeneralPath collapsedShape = new GeneralPath();
+
+ // start point
+ float x0 = (float)nodePoint.getX();
+ float y0 = (float)nodePoint.getY();
+
+ // end point
+ float x1 = (float)maxXPos;
+ float y1 = (float)minYPos;
+
+ float y2 = (float)maxYPos;
+
+ collapsedShape.moveTo(x0, y0);
+ collapsedShape.lineTo(x1, y1);
+ collapsedShape.lineTo(x1, y2);
+ collapsedShape.closePath();
+
+ // add the collapsedShape to the map of branch paths
+ cache.collapsedShapes.put(node, collapsedShape);
+
+ Line2D nodeLabelPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() + 1.0, nodePoint.getY());
+
+ cache.nodeLabelPaths.put(node, nodeLabelPath);
+
+ Line2D nodeBarPath = new Line2D.Double(
+ nodePoint.getX(), nodePoint.getY(),
+ nodePoint.getX() - 1.0, nodePoint.getY());
+
+ cache.nodeBarPaths.put(node, nodeBarPath);
+
+ Line2D tipLabelPath;
+
+ if (alignTipLabels) {
+
+ tipLabelPath = new Line2D.Double(
+ maxXPosition, yPos,
+ maxXPosition + 1.0, yPos);
+
+ Line2D calloutPath = new Line2D.Double(
+ maxXPos, yPos,
+ maxXPosition, yPos);
+
+ cache.calloutPaths.put(node, calloutPath);
+
+ } else {
+ tipLabelPath = new Line2D.Double(
+ maxXPos, yPos,
+ maxXPos + 1.0, yPos);
+
+ }
+
+ cache.tipLabelPaths.put(node, tipLabelPath);
+
+ return nodePoint;
+ }
+
+
+ private void getMaxXPosition(RootedTree tree, Node node, double xPosition) {
+
+ if (!tree.isExternal(node)) {
+
+ List<Node> children = tree.getChildren(node);
+
+ for (Node child : children) {
+ double length = tree.getLength(child);
+ getMaxXPosition(tree, child, xPosition + length);
+ }
+
+ } else {
+ if (xPosition > maxXPosition) {
+ maxXPosition = xPosition;
+ }
+ }
+ }
+
+ private double yPosition;
+ private double yIncrement;
+
+ private double maxXPosition;
+
+
+}
\ No newline at end of file
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayoutController.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayoutController.java
new file mode 100644
index 0000000..41cd617
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/RectilinearTreeLayoutController.java
@@ -0,0 +1,107 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import org.virion.jam.controlpalettes.AbstractController;
+import org.virion.jam.panels.OptionsPanel;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: RectilinearTreeLayoutController.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class RectilinearTreeLayoutController extends AbstractController {
+
+ private static final String RECTILINEAR_LAYOUT_KEY = "rectilinearLayout";
+
+ private static final String ROOT_LENGTH_KEY = "rootLength";
+ private static final String CURVATURE_KEY = "curvature";
+ private static final String ALIGN_TIP_LABELS_KEY = "alignTipLabels";
+
+ public RectilinearTreeLayoutController(final RectilinearTreeLayout treeLayout) {
+ this.treeLayout = treeLayout;
+
+ titleLabel = new JLabel("Rectangular Layout");
+ optionsPanel = new OptionsPanel();
+
+ final int sliderMax = 10000;
+ rootLengthSlider = new JSlider(SwingConstants.HORIZONTAL, 0, sliderMax, 0);
+ rootLengthSlider.setOpaque(false);
+ rootLengthSlider.setValue((int) (treeLayout.getRootLength() * sliderMax));
+ //rootLengthSlider.setMajorTickSpacing(rootLengthSlider.getMaximum() / 5);
+// rootLengthSlider.setPaintTicks(true);
+
+ rootLengthSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = rootLengthSlider.getValue();
+ treeLayout.setRootLength(value / sliderMax);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Root Length:", rootLengthSlider, true);
+
+ curvatureSlider = new JSlider(SwingConstants.HORIZONTAL, 0, sliderMax, 0);
+ curvatureSlider.setOpaque(false);
+ curvatureSlider.setValue((int) (treeLayout.getCurvature() * sliderMax));
+ //curvatureSlider.setMajorTickSpacing(curvatureSlider.getMaximum() / 5);
+ // curvatureSlider.setPaintTicks(true);
+
+ curvatureSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ double value = curvatureSlider.getValue();
+ treeLayout.setCurvature(value / sliderMax);
+ }
+ });
+ optionsPanel.addComponentWithLabel("Curvature:", curvatureSlider, true);
+
+ alignTipLabelsCheck = new JCheckBox("Align Tip Labels");
+ alignTipLabelsCheck.setOpaque(false);
+
+ alignTipLabelsCheck.setSelected(treeLayout.isAlignTipLabels());
+ alignTipLabelsCheck.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ treeLayout.setAlignTipLabels(alignTipLabelsCheck.isSelected());
+ }
+ });
+ optionsPanel.addComponent(alignTipLabelsCheck);
+ }
+
+ public JComponent getTitleComponent() {
+ return titleLabel;
+ }
+
+ public JPanel getPanel() {
+ return optionsPanel;
+ }
+
+ public boolean isInitiallyVisible() {
+ return false;
+ }
+
+ public void initialize() {
+ // nothing to do
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ rootLengthSlider.setValue((Integer) settings.get(RECTILINEAR_LAYOUT_KEY + "." + ROOT_LENGTH_KEY));
+ curvatureSlider.setValue((Integer) settings.get(RECTILINEAR_LAYOUT_KEY + "." + CURVATURE_KEY));
+ alignTipLabelsCheck.setSelected((Boolean) settings.get(RECTILINEAR_LAYOUT_KEY + "." + ALIGN_TIP_LABELS_KEY));
+ }
+
+ public void getSettings(Map<String, Object> settings) {
+ settings.put(RECTILINEAR_LAYOUT_KEY + "." + ROOT_LENGTH_KEY, rootLengthSlider.getValue());
+ settings.put(RECTILINEAR_LAYOUT_KEY + "." + CURVATURE_KEY, curvatureSlider.getValue());
+ settings.put(RECTILINEAR_LAYOUT_KEY + "." + ALIGN_TIP_LABELS_KEY, alignTipLabelsCheck.isSelected());
+ }
+
+ private final JLabel titleLabel;
+ private final OptionsPanel optionsPanel;
+
+ private final JSlider rootLengthSlider;
+ private final JSlider curvatureSlider;
+ private final JCheckBox alignTipLabelsCheck;
+
+ private final RectilinearTreeLayout treeLayout;
+
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayout.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayout.java
new file mode 100644
index 0000000..79a0194
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayout.java
@@ -0,0 +1,101 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import jebl.evolution.trees.RootedTree;
+
+import java.awt.*;
+import java.awt.geom.Point2D;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeLayout.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public interface TreeLayout {
+
+ public enum AxisType {
+ CONTINUOUS,
+ DISCRETE
+ }
+
+ void layout(RootedTree tree, TreeLayoutCache cache);
+
+ /**
+ * Add a listener for this layout
+ *
+ * @param listener
+ */
+ void addTreeLayoutListener(TreeLayoutListener listener);
+
+ /**
+ * Remove a listener from this layout
+ *
+ * @param listener
+ */
+ void removeTreeLayoutListener(TreeLayoutListener listener);
+
+ /**
+ * Return whether the x axis is continuous or discrete
+ *
+ * @return the axis type
+ */
+ AxisType getXAxisType();
+
+ /**
+ * Return whether the y axis is continuous or discrete
+ *
+ * @return the axis type
+ */
+ AxisType getYAxisType();
+
+ /**
+ * Return whether this layout displays a root branch
+ * @return showing root branch?
+ */
+ boolean isShowingRootBranch();
+
+ /**
+ * Return whether this layout is showing a branch colouring
+ * @return showing colouring?
+ */
+ boolean isShowingColouring();
+
+ /**
+ * Return whether the two axis scales should be maintained
+ * relative to each other
+ *
+ * @return a boolean
+ */
+ boolean maintainAspectRatio();
+
+ double getHeightOfPoint(Point2D point);
+
+ /**
+ * Return a line that defines a particular height. Some layouts
+ * won't be able to produce this and may throw an UnsupportedOperationException.
+ *
+ * @param height
+ * @return the line
+ */
+ Shape getHeightLine(double height);
+
+ /**
+ * Return a shape that defines a particular height interval. Some layouts
+ * won't be able to produce this and may throw an UnsupportedOperationException.
+ *
+ * @param height1
+ * @param height2
+ * @return the area
+ */
+ Shape getHeightArea(double height1, double height2);
+
+ String getBranchColouringAttributeName();
+
+ void setBranchColouringAttributeName(String colouringAttributeName);
+
+ String getCartoonAttributeName();
+
+ void setCartoonAttributeName(String cartoonAttributeName);
+
+ String getCollapsedAttributeName();
+
+ void setCollapsedAttributeName(String collapsedAttributeName);
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutCache.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutCache.java
new file mode 100644
index 0000000..f627b80
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutCache.java
@@ -0,0 +1,94 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+import jebl.evolution.graphs.Node;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeLayoutCache.java 535 2006-11-21 11:11:20Z rambaut $
+ */
+public class TreeLayoutCache {
+ public Point2D getNodePoint(Node node) {
+ return nodePoints.get(node);
+ }
+
+ public Shape getBranchPath(Node node) {
+ return branchPaths.get(node);
+ }
+
+ public Map<Node, Shape> getBranchPathMap() {
+ return branchPaths;
+ }
+
+ public Shape getCollapsedShape(Node node) {
+ return collapsedShapes.get(node);
+ }
+
+ public Map<Node, Shape> getCollapsedShapeMap() {
+ return collapsedShapes;
+ }
+
+ public Line2D getTipLabelPath(Node node) {
+ return tipLabelPaths.get(node);
+ }
+
+ public Map<Node, Line2D> getTipLabelPathMap() {
+ return tipLabelPaths;
+ }
+
+ public Line2D getBranchLabelPath(Node node) {
+ return branchLabelPaths.get(node);
+ }
+
+ public Map<Node, Line2D> getBranchLabelPathMap() {
+ return branchLabelPaths;
+ }
+
+ public Line2D getNodeLabelPath(Node node) {
+ return nodeLabelPaths.get(node);
+ }
+
+ public Map<Node, Line2D> getNodeLabelPathMap() {
+ return nodeLabelPaths;
+ }
+
+ public Line2D getNodeBarPath(Node node) {
+ return nodeBarPaths.get(node);
+ }
+
+ public Map<Node, Line2D> getNodeBarPathMap() {
+ return nodeBarPaths;
+ }
+
+ public Shape getCalloutPath(Node node) {
+ return calloutPaths.get(node);
+ }
+
+ public Map<Node, Shape> getCalloutPathMap() {
+ return calloutPaths;
+ }
+
+ public void clear() {
+ nodePoints.clear();
+ branchPaths.clear();
+ collapsedShapes.clear();
+ tipLabelPaths.clear();
+ branchLabelPaths.clear();
+ nodeLabelPaths.clear();
+ nodeBarPaths.clear();
+ calloutPaths.clear();
+}
+
+ protected Map<Node, Point2D> nodePoints = new HashMap<Node, Point2D>();
+ protected Map<Node, Shape> branchPaths = new HashMap<Node, Shape>();
+ protected Map<Node, Shape> collapsedShapes = new HashMap<Node, Shape>();
+ protected Map<Node, Line2D> tipLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Line2D> branchLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Line2D> nodeLabelPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Line2D> nodeBarPaths = new HashMap<Node, Line2D>();
+ protected Map<Node, Shape> calloutPaths = new HashMap<Node, Shape>();
+}
diff --git a/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutListener.java b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutListener.java
new file mode 100644
index 0000000..1248ae0
--- /dev/null
+++ b/src/jebl/gui/trees/treeviewer_dev/treelayouts/TreeLayoutListener.java
@@ -0,0 +1,11 @@
+package jebl.gui.trees.treeviewer_dev.treelayouts;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: TreeLayoutListener.java 294 2006-04-14 10:28:11Z rambaut $
+ */
+public interface TreeLayoutListener {
+
+ void treeLayoutChanged();
+
+}
diff --git a/src/jebl/math/Binomial.java b/src/jebl/math/Binomial.java
new file mode 100644
index 0000000..2a8777d
--- /dev/null
+++ b/src/jebl/math/Binomial.java
@@ -0,0 +1,79 @@
+/*
+ * Binomial.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.math;
+
+/**
+ * Binomial coefficients
+ *
+ * @version $Id: Binomial.java 305 2006-04-26 00:22:30Z rambaut $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @author Korbinian Strimmer
+ */
+public class Binomial
+{
+ //
+ // Public stuff
+ //
+
+ /**
+ * Binomial coefficient n choose k
+ */
+ public static double choose(double n, double k)
+ {
+ n = Math.floor(n + 0.5);
+ k = Math.floor(k + 0.5);
+
+ double lchoose = GammaFunction.lnGamma(n + 1.0) -
+ GammaFunction.lnGamma(k + 1.0) - GammaFunction.lnGamma(n - k + 1.0);
+
+ return Math.floor(Math.exp(lchoose) + 0.5);
+ }
+
+ /**
+ * get n choose 2
+ */
+ public static double choose2(int n)
+ {
+ // not sure how much overhead there is with try-catch blocks
+ // i.e. would an if statement be better?
+
+ try {
+ return choose2LUT[n];
+
+ } catch (ArrayIndexOutOfBoundsException e) {
+
+ while (maxN < n) {
+ maxN += 1000;
+ }
+
+ initialize();
+ return choose2LUT[n];
+ }
+ }
+
+ private static void initialize() {
+ choose2LUT = new double[maxN+1];
+ choose2LUT[0] = 0;
+ choose2LUT[1] = 0;
+ choose2LUT[2] = 1;
+ for (int i = 3; i <= maxN; i++) {
+ choose2LUT[i] = ((double) (i*(i-1))) * 0.5;
+ }
+ }
+
+ private static int maxN = 5000;
+ private static double[] choose2LUT;
+
+ static {
+ initialize();
+ }
+}
diff --git a/src/jebl/math/GammaFunction.java b/src/jebl/math/GammaFunction.java
new file mode 100644
index 0000000..fa8362d
--- /dev/null
+++ b/src/jebl/math/GammaFunction.java
@@ -0,0 +1,204 @@
+/*
+ * GammaFunction.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+
+package jebl.math;
+
+
+/**
+ * gamma function
+ *
+ * @version $Id: GammaFunction.java 305 2006-04-26 00:22:30Z rambaut $
+ *
+ * @author Korbinian Strimmer
+ */
+public class GammaFunction
+{
+ //
+ // Public stuff
+ //
+
+
+ // Gamma function
+
+ /**
+ * log Gamma function: ln(gamma(alpha)) for alpha>0, accurate to 10 decimal places
+ *
+ * @param alpha argument
+ */
+ public static double lnGamma(double alpha)
+ {
+ // Pike MC & Hill ID (1966) Algorithm 291: Logarithm of the gamma function.
+ // Communications of the Association for Computing Machinery, 9:684
+
+ double x = alpha, f = 0.0, z;
+
+ if (x < 7)
+ {
+ f = 1;
+ z = x-1;
+ while (++z < 7)
+ {
+ f *= z;
+ }
+ x = z;
+ f = -Math.log(f);
+ }
+ z = 1/(x*x);
+
+ return
+ f + (x-0.5)*Math.log(x) - x + 0.918938533204673 +
+ (((-0.000595238095238*z+0.000793650793651) *
+ z-0.002777777777778)*z + 0.083333333333333)/x;
+ }
+
+ /**
+ * Incomplete Gamma function Q(a,x)
+ * (a cleanroom implementation of Numerical Recipes gammq(a,x);
+ * in Mathematica this function is called GammaRegularized)
+ *
+ * @param a parameter
+ * @param x argument
+ *
+ * @return function value
+ */
+ public static double incompleteGammaQ(double a, double x)
+ {
+ return 1.0 - incompleteGamma(x, a, lnGamma(a));
+ }
+
+ /**
+ * Incomplete Gamma function P(a,x) = 1-Q(a,x)
+ * (a cleanroom implementation of Numerical Recipes gammp(a,x);
+ * in Mathematica this function is 1-GammaRegularized)
+ *
+ * @param a parameter
+ * @param x argument
+ *
+ * @return function value
+ */
+ public static double incompleteGammaP(double a, double x)
+ {
+ return incompleteGamma(x, a, lnGamma(a));
+ }
+
+ /**
+ * Incomplete Gamma function P(a,x) = 1-Q(a,x)
+ * (a cleanroom implementation of Numerical Recipes gammp(a,x);
+ * in Mathematica this function is 1-GammaRegularized)
+ *
+ * @param a parameter
+ * @param x argument
+ * @param lnGammaA precomputed lnGamma(a)
+ *
+ * @return function value
+ */
+ public static double incompleteGammaP(double a, double x, double lnGammaA)
+ {
+ return incompleteGamma(x, a, lnGammaA);
+ }
+
+
+ /**
+ * Returns the incomplete gamma ratio I(x,alpha) where x is the upper
+ * limit of the integration and alpha is the shape parameter.
+ */
+ private static double incompleteGamma(double x, double alpha, double ln_gamma_alpha)
+ {
+ // (1) series expansion if (alpha>x || x<=1)
+ // (2) continued fraction otherwise
+ // RATNEST FORTRAN by
+ // Bhattacharjee GP (1970) The incomplete gamma integral. Applied Statistics,
+ // 19: 285-287 (AS32)
+
+ int i;
+ double p = alpha, g = ln_gamma_alpha;
+ double accurate = 1e-8, overflow = 1e30;
+ double factor, gin = 0, rn = 0, a = 0,b = 0,an = 0, dif = 0, term = 0;
+ double pn0, pn1, pn2, pn3, pn4, pn5;
+
+ if (x == 0.0)
+ {
+ return 0.0;
+ }
+ if ( x < 0.0 || p <= 0.0)
+ {
+ throw new IllegalArgumentException("Arguments out of bounds");
+ }
+
+ factor = Math.exp(p*Math.log(x)-x-g);
+
+ if (x > 1 && x >= p)
+ {
+ // continued fraction
+ a = 1-p;
+ b = a+x+1;
+ term = 0;
+ pn0 = 1;
+ pn1 = x;
+ pn2 = x+1;
+ pn3 = x*b;
+ gin = pn2/pn3;
+
+ do
+ {
+ a++;
+ b += 2;
+ term++;
+ an = a*term;
+ pn4 = b*pn2-an*pn0;
+ pn5 = b*pn3-an*pn1;
+
+ if (pn5 != 0)
+ {
+ rn = pn4/pn5;
+ dif = Math.abs(gin-rn);
+ if (dif <= accurate)
+ {
+ if (dif <= accurate*rn)
+ {
+ break;
+ }
+ }
+
+ gin=rn;
+ }
+ pn0 = pn2;
+ pn1 = pn3;
+ pn2 = pn4;
+ pn3 = pn5;
+ if (Math.abs(pn4) >= overflow)
+ {
+ pn0 /= overflow;
+ pn1 /= overflow;
+ pn2 /= overflow;
+ pn3 /= overflow;
+ }
+ } while (true);
+ gin = 1-factor*gin;
+ }
+ else
+ {
+ // series expansion
+ gin = 1;
+ term = 1;
+ rn = p;
+ do
+ {
+ rn++;
+ term *= x/rn;
+ gin += term;
+ }
+ while (term > accurate);
+ gin *= factor/p;
+ }
+ return gin;
+ }
+
+}
diff --git a/src/jebl/math/MachineAccuracy.java b/src/jebl/math/MachineAccuracy.java
new file mode 100644
index 0000000..336da3a
--- /dev/null
+++ b/src/jebl/math/MachineAccuracy.java
@@ -0,0 +1,87 @@
+// MachineAccuracy.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * determines machine accuracy
+ *
+ * @version $Id: MachineAccuracy.java 650 2007-03-12 20:09:10Z twobeers $
+ *
+ * @author Korbinian Strimmer
+ * @author Alexei Drummond
+ */
+public class MachineAccuracy
+{
+ //
+ // Public stuff
+ //
+
+ /** machine accuracy constant */
+ public static double EPSILON = 2.220446049250313E-16;
+
+ public static double SQRT_EPSILON = 1.4901161193847656E-8;
+ public static double SQRT_SQRT_EPSILON = 1.220703125E-4;
+
+ /** compute EPSILON from scratch */
+ public static double computeEpsilon()
+ {
+ double eps = 1.0;
+
+ while( eps + 1.0 != 1.0 )
+ {
+ eps /= 2.0;
+ }
+ eps *= 2.0;
+
+ return eps;
+ }
+
+ /*
+
+ // Convenience methods proposed by Tobias:
+ public static boolean strictlySmaller(double a, double b) {
+ return (a < b) && !same(a,b);
+ }
+
+ public static boolean strictlyLarger(double a, double b) {
+ return strictlySmaller(b,a);
+ }
+
+ public static boolean smallerOrEqual(double a, double b) {
+ // same as !strictlyLarger(a,b)
+ return (a < b) || MachineAccuracy.same(a,b);
+ }
+
+ public static boolean largerOrEqual(double a, double b) {
+ return !smallerOrEqual(b,a);
+ } */
+
+ /**
+ * @return true if the relative difference between the two parameters
+ * is no larger than SQRT_EPSILON.
+ *
+ * (TT: I think this means (b / (1+SQRT_EPSILON)) <= a <= b * (1+SQRT_EPSILON) )
+ */
+ public static boolean same(double a, double b) {
+ // Tobias: I changed the formula on 2006-12-14. The old version had two
+ // problems:
+ // 1.) same(0,0) was false (because of a division by zero)
+ // 2.) same() was asymmetric: let a = 1.0, b = 1.0 - MachineAccuracy.SQRT_EPSILON.
+ // Then same(a,b) == false and same(b,a) == true with the old version of same().
+ //return Math.abs((a/b)-1.0) <= SQRT_EPSILON;
+ if ((a < 0) != (b < 0)) {
+ return false;
+ } else {
+ a = Math.abs(a);
+ b = Math.abs(b);
+ return ((b / (1.0+SQRT_EPSILON)) <= a) && (a <= (1+SQRT_EPSILON) * b);
+ }
+ }
+}
diff --git a/src/jebl/math/MatrixCalc.java b/src/jebl/math/MatrixCalc.java
new file mode 100644
index 0000000..7ecdff2
--- /dev/null
+++ b/src/jebl/math/MatrixCalc.java
@@ -0,0 +1,320 @@
+package jebl.math;
+
+
+
+/**
+ * @author Stephen A. Smith
+ *
+ */
+
+public final class MatrixCalc {
+ /**
+ * Cholesky factorization (aka Cholesky Decomposition)
+ *
+ * This factorization can be used when square matrix is symmetric and positive definite.
+ * It is much faster than other methods where symmetry is ignored (LU Decomposition).
+ *
+ * @param inMatrix square symmetric matrix to perform cholesky factorization
+ * @return resulting matrix
+ */
+ public static double [][] choleskyFactor(double [][] inMatrix)
+ throws MatrixCalcException.NotSquareMatrixException,
+ MatrixCalcException.PositiveDefiniteException{
+ int j;
+ if ( inMatrix.length != inMatrix[0].length ){
+ throw new MatrixCalcException.NotSquareMatrixException("error in CholeskyFactor, not a square matrix");
+ }
+ //should be ok because it has to be a symmetrical square matrix
+ double [][] inMatrix_ent = inMatrix;
+ for (int k=0; k<inMatrix.length; k++ )
+ {
+ double sum = inMatrix_ent[k][k];
+ double [] inMatrix_piv = inMatrix_ent[k];
+ for (j=0; j<k; j++ )
+ {
+ double tmp = inMatrix_ent[k][j];
+ sum -= tmp*tmp;
+ }
+ if ( sum <= 0.0 ){
+ throw new MatrixCalcException.PositiveDefiniteException("error in CholeskyFactor, sum");
+ }
+ inMatrix_ent[k][k] = Math.sqrt(sum);
+ for (int i=k+1; i<inMatrix.length; i++ ){
+ sum = inMatrix_ent[i][k];
+ inMatrix_piv = inMatrix_ent[k];
+ double [] inMatrix_row = inMatrix_ent[i];
+ for (j=0; j<k; j++ ){
+ sum -= inMatrix_ent[i][j]*inMatrix_ent[k][j];
+ sum -= (inMatrix_row[j])*(inMatrix_piv[j]);
+ }
+ inMatrix_ent[j][i] = inMatrix_ent[i][j] = sum/inMatrix_ent[k][k];
+ }
+ }
+ return inMatrix;
+ }
+
+ /**
+ * Cholesky solve
+ *
+ * Once the matrix is decomposed with the above routine, one can solve the triangular factor with backsubstitution.
+ * The forward (lowerSolve) and backward (upperSolve) are used for this.
+ *
+ * @param matrix matrix to perform cholesky solve (probably used after factorization)
+ * @param vector vector to solve matrix * vector = return
+ * @return the resulting vector
+ */
+ public static double [] choleskySolve(double [][] matrix, double [] vector)
+ throws MatrixCalcException.NotSquareMatrixException{
+ if ( matrix.length != matrix[0].length || matrix[0].length != vector.length ){
+ throw new MatrixCalcException.NotSquareMatrixException("error in CholeskySolve, not a square matrix");
+ }
+ double [] retVector = new double [vector.length];
+ retVector = lowerSolve(matrix, vector, 0.0);
+ retVector = upperSolve(matrix, retVector, 0.0);
+ return retVector;
+ }
+
+ /**
+ * lower Solve
+ * forward elimination with (optional) default diagonal value
+ *
+ * @param matrix the matrix to perform the forward elimination
+ * @param vector
+ * @param diag the default diagonal value
+ * @return the resulting vector
+ */
+ public static double [] lowerSolve(double [][] matrix, double [] vector, double diag){
+ int dimension, i;
+ double [] out = new double [vector.length];
+ if(matrix.length<matrix[0].length)
+ dimension = matrix.length;
+ else
+ dimension=matrix[0].length;
+ if ( vector.length < dimension ){
+ System.out.println("error in LSolve, problem with vector length");
+ System.exit(0);
+ }
+ if (out.length < dimension )
+ out = new double [matrix.length];
+ double [][] mat_ent = matrix;
+ double [] vector_ent = vector;
+ double [] out_ent = out;
+ for ( i=0; i<dimension; i++ ){
+ if ( vector_ent[i] != 0.0 )
+ break;
+ else
+ out_ent[i] = 0.0;
+ }
+ int i_lim = i;
+ for ( ; i<dimension; i++ ){
+ double sum = vector_ent[i];
+ for ( int j=i_lim; j<i; j++ ){
+ sum -= mat_ent[i][j]*out_ent[j];
+ }
+ if ( diag==0.0 ){
+ if ( mat_ent[i][i]==0.0 ){
+ System.out.println("error in LSolve, error in matrix");
+ System.exit(0);
+ }
+ else
+ out_ent[i] = sum/mat_ent[i][i];
+ }
+ else
+ out_ent[i] = sum/diag;
+ }
+ return out;
+ }
+
+ /**
+ *
+ * upperSolve
+ * back substitution with optional over-riding diagonal
+ *
+ * @param matrix the matrix to perform the back substitution
+ * @param vector
+ * @param diag the default diagonal value
+ * @return the resulting vector
+ */
+ public static double [] upperSolve(double [][] matrix, double[] vector, double diag){
+ int dimension;
+ int i, ilim=0;
+ double [] out = new double [matrix.length];
+ if(matrix.length < matrix[0].length)
+ dimension = matrix.length;
+ else
+ dimension=matrix[0].length;
+ if ( vector.length < dimension ){
+ System.out.println("error in upperSolve");
+ System.exit(1);
+ }
+ if ( out==null || out.length < dimension )
+ out = new double [matrix.length];
+ if (out.length < dimension )
+ out = new double [matrix.length];
+ double [][] matrix_ent = matrix;
+ double [] vector_ent = vector;
+ double [] out_ent = out;
+ for ( i=dimension-1; i>=0; i-- ){
+ if ( vector_ent[i] != 0.0 )
+ break;
+ else
+ out_ent[i] = 0.0;
+ }
+ ilim = i;
+ //use i start value from the above loop
+ for (; i>=0; i-- ){
+ double sum = vector_ent[i];
+ for (int j=i+1; j<=ilim; j++ ){
+ sum -= matrix_ent[i][j]*out_ent[j];
+ }
+ if ( diag==0.0 ){
+ if ( matrix_ent[i][i]==0.0 ){
+ System.out.println("error in USolve");
+ System.exit(1);
+ }
+ else
+ out_ent[i] = sum/matrix_ent[i][i];
+ }
+ else
+ out_ent[i] = sum/diag;
+ }
+ return out;
+ }
+
+ /**
+ * innerProdect
+ * calculates inner product of two vectors from i down
+ *
+ * @param vector1 the first vector
+ * @param vector2 the second vector
+ * @param x the starting int
+ * @return the inner product of the two vectors starting from x
+ */
+ public static double innerProduct(double [] vector1, double [] vector2, int x)throws IndexOutOfBoundsException{
+ int length;
+ double sum = 0;
+ if(vector1.length<vector2.length)
+ length = vector1.length;
+ else
+ length=vector2.length;
+ if ( x > length ){
+ throw new IndexOutOfBoundsException("innerProduct int x out of vector bounds");
+ }
+ for (int i=x; i<length; i++ )
+ sum += vector1[i]*vector2[i];
+ return sum;
+ }
+
+ /**
+ * takes a matrix and gets a column, then returns it as a vector
+ * @param matrix the matrix from which the column will be returned
+ * @param column the number of the column to return
+ * @return the column as a vector from the input matrix
+ */
+ public static double [] getColumn(double [][] matrix, int column){
+ double [] x = new double [matrix.length];
+ for(int i=0;i<x.length;i++){
+ x[i] = matrix [i][column];
+ }
+ return x;
+ }
+
+ /**
+ * takes a matrix and deletes a row
+ * @param matrix the matrix from which to delete the row
+ * @param row the number of the row to delete
+ * @return the matrix with deleted row
+ */
+ public static double [][] deleteMatrixRow(double [][] matrix, int row){
+ double [][] x = new double [matrix.length-1][matrix[0].length];
+ int j=0;
+ for(int i=0;i<matrix.length;i++){
+ if(i!=row){
+ System.arraycopy(matrix[i], 0, x[j], 0, matrix[i].length);
+ j++;
+ }
+ }
+ return x;
+ }
+
+ /**
+ * takes a matrix and deletes a column
+ * @param matrix the matrix from which to delete the column
+ * @param column the number of the column to delete
+ * @return the matrix with deleted column
+ */
+ public static double [][] deleteMatrixColumn(double [][] matrix, int column){
+ double [][] x = new double [matrix.length][matrix[0].length-1];
+ for(int i=0;i<matrix.length;i++){
+ int j=0;
+ for(int k=0;k<matrix[i].length;k++){
+ if(k!=column){
+ x[i][j]=matrix[i][k];
+ j++;
+ }
+ }
+ }
+ return x;
+ }
+
+ /**
+ * reverse a vector
+ * @param vector the vector to reverse
+ * @return the reversed vector
+ */
+ public static double [] reverseVector(double [] vector){
+ double [] x = new double [vector.length];
+ int j=0;
+ for(int i=vector.length-1;i>=0;i--){
+ x[i]=vector[j];
+ j++;
+ }
+ return x;
+ }
+
+ /**
+ * reverse a matrix
+ * @param matrix the matrix to reverse
+ * @return the reversed matrix
+ */
+ public static double [][] reverseMatrix(double [][] matrix){
+ double [][] x = new double [matrix.length][matrix[0].length];
+ int j=0;
+ for(int i=matrix.length-1;i>=0;i--){
+ int k=0;
+ for(int h=matrix[0].length-1;h>=0;h--){
+ x[j][k]=matrix[i][h];
+ k++;
+ }
+ j++;
+ }
+ return x;
+ }
+
+ /**
+ * sum a vector
+ * @param vector the input vector
+ * @return the sum of the vector
+ */
+ public static double sumVector(double [] vector){
+ double sum = 0.0;
+ for (double aVector : vector) sum += aVector;
+ return sum;
+
+ }
+
+ /**
+ * copy one matrix into another
+ * @param matrix the matrix to copy
+ * @return the copied matrix
+ */
+ public static double [][] copyMatrix(double [][] matrix){
+ double [][] x = new double [matrix.length][matrix[0].length];
+ for(int i=0;i<x.length;i++){
+ for(int j=0;j<x[i].length;j++){
+ x[i][j]=matrix[i][j];
+ }
+ }
+ return x;
+ }
+}
diff --git a/src/jebl/math/MatrixCalcException.java b/src/jebl/math/MatrixCalcException.java
new file mode 100644
index 0000000..23faf2f
--- /dev/null
+++ b/src/jebl/math/MatrixCalcException.java
@@ -0,0 +1,16 @@
+package jebl.math;
+
+
+public class MatrixCalcException extends Exception{
+ public MatrixCalcException() { super(); }
+ public MatrixCalcException(String message) { super(message); }
+
+ public static class NotSquareMatrixException extends MatrixCalcException {
+ public NotSquareMatrixException() { super(); }
+ public NotSquareMatrixException(String message) { super(message); }
+ }
+ public static class PositiveDefiniteException extends MatrixCalcException {
+ public PositiveDefiniteException() { super(); }
+ public PositiveDefiniteException(String message) { super(message); }
+ }
+}
diff --git a/src/jebl/math/MersenneTwisterFast.java b/src/jebl/math/MersenneTwisterFast.java
new file mode 100644
index 0000000..634dbfc
--- /dev/null
+++ b/src/jebl/math/MersenneTwisterFast.java
@@ -0,0 +1,779 @@
+package jebl.math;
+
+/*
+ * MersenneTwisterFast.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+import java.io.Serializable;
+
+/**
+ * MersenneTwisterFast:
+ *
+ * A simulation quality fast random number generator (MT19937)
+ * with the same public methods as java.util.Random.
+
+ * <p>About the Mersenne Twister.
+ * This is a Java version of the C-program for MT19937: Integer version.
+ * next(32) generates one pseudorandom unsigned integer (32bit)
+ * which is uniformly distributed among 0 to 2^32-1 for each
+ * call. next(int bits) >>>'s by (32-bits) to get a value ranging
+ * between 0 and 2^bits-1 long inclusive; hope that's correct.
+ * setSeed(seed) set initial values to the working area
+ * of 624 words. For setSeed(seed), seed is any 32-bit integer
+ * except for 0.
+ *<p>
+ * Reference.
+ * M. Matsumoto and T. Nishimura,
+ * "Mersenne Twister: A 623-Dimensionally Equidistributed Uniform
+ * Pseudo-Random Number Generator",
+ * <i>ACM Transactions on Modeling and Computer Simulation,</i>
+ * Vol. 8, No. 1, January 1998, pp 3--30.
+ *
+ * <p>Bug Fixes. This implementation implements the bug fixes made
+ * in Java 1.2's version of Random, which means it can be used with
+ * earlier versions of Java. See
+ * <a href="http://www.javasoft.com/products/jdk/1.2/docs/api/java/util/Random.html">
+ * the JDK 1.2 java.util.Random documentation</a> for further documentation
+ * on the random-number generation contracts made. Additionally, there's
+ * an undocumented bug in the JDK java.util.Random.nextBytes() method,
+ * which this code fixes.
+ *
+ * <p> Important Note. Just like java.util.Random, this
+ * generator accepts a long seed but doesn't use all of it. java.util.Random
+ * uses 48 bits. The Mersenne Twister instead uses 32 bits (int size).
+ * So it's best if your seed does not exceed the int range.
+ *
+ * <p><a href="http://www.cs.umd.edu/users/seanl/">Sean Luke's web page</a>
+ *
+ * <P>
+ * - added shuffling method (Alexei Drummond)
+ *
+ * This is now package private - it should be accessed using the instance in Random
+ */
+class MersenneTwisterFast implements Serializable {
+ // Period parameters
+ private static final int N = 624;
+ private static final int M = 397;
+ private static final int MATRIX_A = 0x9908b0df; // private static final * constant vector a
+ private static final int UPPER_MASK = 0x80000000; // most significant w-r bits
+ private static final int LOWER_MASK = 0x7fffffff; // least significant r bits
+
+
+ // Tempering parameters
+ private static final int TEMPERING_MASK_B = 0x9d2c5680;
+ private static final int TEMPERING_MASK_C = 0xefc60000;
+
+
+ // #define TEMPERING_SHIFT_U(y) (y >>> 11)
+ // #define TEMPERING_SHIFT_S(y) (y << 7)
+ // #define TEMPERING_SHIFT_T(y) (y << 15)
+ // #define TEMPERING_SHIFT_L(y) (y >>> 18)
+
+ private int mt[]; // the array for the state vector
+ private int mti; // mti==N+1 means mt[N] is not initialized
+ private int mag01[];
+
+ // a good initial seed (of int size, though stored in a long)
+ private static final long GOOD_SEED = 4357;
+
+ private double nextNextGaussian;
+ private boolean haveNextNextGaussian;
+
+ // The following can be accessed externally by the static accessor methods which
+ // inforce synchronization
+ private static final MersenneTwisterFast DEFAULT_INSTANCE = new MersenneTwisterFast();
+
+ // Added to curernt time in default constructor, and then adjust to allow for programs that construct
+ // multiple MersenneTwisterFast in a short amount of time.
+ private static long seedAdditive_ = 0;
+
+ /**
+ * Constructor using the time of day as default seed.
+ */
+ MersenneTwisterFast() {
+ this(System.currentTimeMillis()+seedAdditive_);
+ seedAdditive_+=nextInt();
+ }
+
+ /**
+ * Constructor using a given seed. Though you pass this seed in
+ * as a long, it's best to make sure it's actually an integer.
+ *
+ * @param seed generator starting number, often the time of day.
+ */
+ MersenneTwisterFast(long seed)
+ {
+ if (seed == 0)
+ {
+ setSeed(GOOD_SEED);
+ }
+ else
+ {
+ setSeed(seed);
+ }
+ }
+
+
+ /**
+ * Initalize the pseudo random number generator.
+ * The Mersenne Twister only uses an integer for its seed;
+ * It's best that you don't pass in a long that's bigger
+ * than an int.
+ *
+ * @param seed from constructor
+ *
+ */
+ public final void setSeed(long seed)
+ {
+ haveNextNextGaussian = false;
+
+ mt = new int[N];
+
+ // setting initial seeds to mt[N] using
+ // the generator Line 25 of Table 1 in
+ // [KNUTH 1981, The Art of Computer Programming
+ // Vol. 2 (2nd Ed.), pp102]
+
+ // the 0xffffffff is commented out because in Java
+ // ints are always 32 bits; hence i & 0xffffffff == i
+
+ mt[0]= ((int)seed); // & 0xffffffff;
+
+ for (mti = 1; mti < N; mti++)
+ mt[mti] = (69069 * mt[mti-1]); //& 0xffffffff;
+
+ // mag01[x] = x * MATRIX_A for x=0,1
+ mag01 = new int[2];
+ mag01[0] = 0x0;
+ mag01[1] = MATRIX_A;
+ }
+
+ public final int nextInt()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return y;
+ }
+
+
+
+ public final short nextShort()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (short)(y >>> 16);
+ }
+
+
+
+ public final char nextChar()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (char)(y >>> 16);
+ }
+
+
+ public final boolean nextBoolean()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (boolean)((y >>> 31) != 0);
+ }
+
+
+ public final byte nextByte()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (byte)(y >>> 24);
+ }
+
+
+ public final void nextBytes(byte[] bytes)
+ {
+ int y;
+
+ for (int x=0;x<bytes.length;x++)
+ {
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ bytes[x] = (byte)(y >>> 24);
+ }
+ }
+
+
+ public final long nextLong()
+ {
+ int y;
+ int z;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ z = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (z >>> 1) ^ mag01[z & 0x1];
+
+ mti = 0;
+ }
+
+ z = mt[mti++];
+ z ^= z >>> 11; // TEMPERING_SHIFT_U(z)
+ z ^= (z << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(z)
+ z ^= (z << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(z)
+ z ^= (z >>> 18); // TEMPERING_SHIFT_L(z)
+
+ return (((long)y) << 32) + (long)z;
+ }
+
+
+ public final double nextDouble()
+ {
+ int y;
+ int z;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ z = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (z >>> 1) ^ mag01[z & 0x1];
+
+ mti = 0;
+ }
+
+ z = mt[mti++];
+ z ^= z >>> 11; // TEMPERING_SHIFT_U(z)
+ z ^= (z << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(z)
+ z ^= (z << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(z)
+ z ^= (z >>> 18); // TEMPERING_SHIFT_L(z)
+
+ /* derived from nextDouble documentation in jdk 1.2 docs, see top */
+ return ((((long)(y >>> 6)) << 27) + (z >>> 5)) / (double)(1L << 53);
+ }
+
+ public final double nextGaussian()
+ {
+ if (haveNextNextGaussian)
+ {
+ haveNextNextGaussian = false;
+ return nextNextGaussian;
+ }
+ else
+ {
+ double v1, v2, s;
+ do
+ {
+ int y;
+ int z;
+ int a;
+ int b;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ z = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (z >>> 1) ^ mag01[z & 0x1];
+ }
+ z = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (z >>> 1) ^ mag01[z & 0x1];
+
+ mti = 0;
+ }
+
+ z = mt[mti++];
+ z ^= z >>> 11; // TEMPERING_SHIFT_U(z)
+ z ^= (z << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(z)
+ z ^= (z << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(z)
+ z ^= (z >>> 18); // TEMPERING_SHIFT_L(z)
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ a = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (a >>> 1) ^ mag01[a & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ a = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (a >>> 1) ^ mag01[a & 0x1];
+ }
+ a = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (a >>> 1) ^ mag01[a & 0x1];
+
+ mti = 0;
+ }
+
+ a = mt[mti++];
+ a ^= a >>> 11; // TEMPERING_SHIFT_U(a)
+ a ^= (a << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(a)
+ a ^= (a << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(a)
+ a ^= (a >>> 18); // TEMPERING_SHIFT_L(a)
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ b = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (b >>> 1) ^ mag01[b & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ b = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (b >>> 1) ^ mag01[b & 0x1];
+ }
+ b = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (b >>> 1) ^ mag01[b & 0x1];
+
+ mti = 0;
+ }
+
+ b = mt[mti++];
+ b ^= b >>> 11; // TEMPERING_SHIFT_U(b)
+ b ^= (b << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(b)
+ b ^= (b << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(b)
+ b ^= (b >>> 18); // TEMPERING_SHIFT_L(b)
+
+ /* derived from nextDouble documentation in jdk 1.2 docs, see top */
+ v1 = 2 *
+ (((((long)(y >>> 6)) << 27) + (z >>> 5)) / (double)(1L << 53))
+ - 1;
+ v2 = 2 * (((((long)(a >>> 6)) << 27) + (b >>> 5)) / (double)(1L << 53))
+ - 1;
+ s = v1 * v1 + v2 * v2;
+ } while (s >= 1);
+ double multiplier = Math.sqrt(-2 * Math.log(s)/s);
+ nextNextGaussian = v2 * multiplier;
+ haveNextNextGaussian = true;
+ return v1 * multiplier;
+ }
+ }
+
+ public final float nextFloat()
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (y >>> 8) / ((float)(1 << 24));
+ }
+
+
+
+ /** Returns an integer drawn uniformly from 0 to n-1. Suffice it to say,
+ n must be > 0, or an IllegalArgumentException is raised. */
+ public int nextInt(int n)
+ {
+ if (n<=0)
+ throw new IllegalArgumentException("n must be positive");
+
+ if ((n & -n) == n) // i.e., n is a power of 2
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ return (int)((n * (long) (y >>> 1) ) >> 31);
+ }
+
+ int bits, val;
+ do
+ {
+ int y;
+
+ if (mti >= N) // generate N words at one time
+ {
+ int kk;
+
+ for (kk = 0; kk < N - M; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ for (; kk < N-1; kk++)
+ {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1];
+ }
+ y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+ y ^= y >>> 11; // TEMPERING_SHIFT_U(y)
+ y ^= (y << 7) & TEMPERING_MASK_B; // TEMPERING_SHIFT_S(y)
+ y ^= (y << 15) & TEMPERING_MASK_C; // TEMPERING_SHIFT_T(y)
+ y ^= (y >>> 18); // TEMPERING_SHIFT_L(y)
+
+ bits = (y >>> 1);
+ val = bits % n;
+ } while(bits - val + (n-1) < 0);
+ return val;
+ }
+
+ /**
+ * Shuffles an array.
+ */
+ public final void shuffle(int[] array) {
+ int l = array.length;
+ for (int i = 0; i < l; i++) {
+ int index = nextInt(l-i) + i;
+ int temp = array[index];
+ array[index] = array[i];
+ array[i] = temp;
+ }
+ }
+ /**
+ * Shuffles an array. Shuffles numberOfShuffles times
+ */
+ public final void shuffle(int[] array, int numberOfShuffles) {
+ int i, j, temp, l = array.length;
+ for (int shuffle = 0; shuffle < numberOfShuffles; shuffle++) {
+ do {
+ i = nextInt(l);
+ j = nextInt(l);
+ } while(i!=j);
+ temp = array[j];
+ array[j] = array[i];
+ array[i] = temp;
+ }
+ }
+ /**
+ * Returns an array of shuffled indices of length l.
+ * @param l length of the array required.
+ */
+ public int[] shuffled(int l) {
+
+ int[] array = new int[l];
+
+ // initialize array
+ for (int i = 0; i < l; i++) {
+ array[i] = i;
+ }
+ shuffle(array);
+
+ return array;
+ }
+
+}
diff --git a/src/jebl/math/MinimiserMonitor.java b/src/jebl/math/MinimiserMonitor.java
new file mode 100644
index 0000000..f8453ee
--- /dev/null
+++ b/src/jebl/math/MinimiserMonitor.java
@@ -0,0 +1,146 @@
+// MultivariateMonitor.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.math;
+import java.io.*;
+/**
+ * interface for a classes that wish to monitor the progress of a Minimiser
+ *
+ * @author Matthew Goode
+ */
+
+public interface MinimiserMonitor {
+ /**
+ * Inform monitor of current progress (as a number between 0 and 1), or -1 to reset
+ */
+ public void updateProgress(double progress);
+
+ /**
+ * Inform monitor of a new minimum, along with the current arguments. Monitors should NOT
+ * change the supplied array of parameterValues!
+ * This should be called in the same thread as the minimisation so that beingOptimized may be accessed
+ * within this call with out worry of conflicting with the optimisation process!
+ */
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction beingOptimized);
+//=====================================================================
+//=====================================================================
+
+ public static class Utils {
+
+ /**
+ * Creates a MinimiserMonitor that outputs current minimum to a print stream
+ */
+ public static final MinimiserMonitor createSimpleMonitor(PrintWriter output) {
+ return new Simple(output);
+ }
+ /**
+ * creates a monitor such that all information sent to monitor is based on two sub monitors
+ */
+ public static final MinimiserMonitor createSplitMonitor(MinimiserMonitor a, MinimiserMonitor b) {
+ return new Split(a,b);
+ }
+
+ /**
+ * Creates a MinimiserMonitor that outputs current minimum to a System.out
+ */
+ public static final MinimiserMonitor createSystemOuptutMonitor() {
+ return SystemOutput.INSTANCE;
+ }
+ /**
+ * Creates a MinimiserMonitor that outputs current minimum to a System.err
+ */
+ public static final MinimiserMonitor createSystemErrorMonitor() {
+ return SystemError.INSTANCE;
+ }
+ /**
+ * Creates a MinimiserMonitor that Stores output (use toString() to access current results)
+ */
+ public static final MinimiserMonitor createStringMonitor() {
+ return new StringMonitor();
+ }
+ /**
+ * Creates a MinimiserMonitor that looses all output
+ */
+ public static final MinimiserMonitor createNullMonitor() {
+ return NullMonitor.INSTANCE;
+ }
+
+
+ //=============================================================
+ private static final class StringMonitor implements MinimiserMonitor {
+ private final StringWriter sw_;
+ private final PrintWriter pw_;
+ public StringMonitor() {
+ this.sw_ = new StringWriter();
+ this.pw_ = new PrintWriter(sw_,true);
+ }
+ public void updateProgress(double progress) {
+ pw_.println("Update Progress:"+progress);
+ }
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction beingOptimized) {
+ pw_.println("New Minimum:"+value);
+ }
+ public String toString() {
+ return sw_.toString();
+ }
+ }
+ //=============================================================
+ private static final class NullMonitor implements MinimiserMonitor {
+ public static final MinimiserMonitor INSTANCE = new NullMonitor();
+ public NullMonitor() { }
+ public void updateProgress(double progress) {}
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction beingOptimized) {}
+ public String toString() { return "Null Monitor"; }
+ }
+
+
+ private static class Split implements MinimiserMonitor {
+ private final MinimiserMonitor a_;
+ private final MinimiserMonitor b_;
+
+ public Split(MinimiserMonitor a, MinimiserMonitor b) {
+ this.a_ = a; this.b_ = b;
+ }
+ public void updateProgress(double progress) {
+ a_.updateProgress(progress);
+ b_.updateProgress(progress);
+ }
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction mf) {
+ a_.newMinimum(value,parameterValues,mf);
+ b_.newMinimum(value,parameterValues,mf);
+ }
+ }
+ private static class Simple implements MinimiserMonitor {
+ PrintWriter output_;
+ Simple(PrintWriter output) {
+ this.output_ = output;
+ }
+ public void updateProgress(double progress) { }
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction mf) {
+ output_.println("New Minimum:"+value);
+ }
+ }
+ private static class SystemOutput implements MinimiserMonitor {
+ static final SystemOutput INSTANCE = new SystemOutput();
+ public void updateProgress(double progress) { }
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction mf) {
+ System.out.println("New Minimum:"+value);
+ }
+ }
+ private static class SystemError implements MinimiserMonitor {
+ static final SystemError INSTANCE = new SystemError();
+ public void updateProgress(double progress) { }
+ public void newMinimum(double value, double[] parameterValues, MultivariateFunction mf) {
+ System.err.println("New Minimum:"+value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/math/MultivariateFunction.java b/src/jebl/math/MultivariateFunction.java
new file mode 100644
index 0000000..b858ee5
--- /dev/null
+++ b/src/jebl/math/MultivariateFunction.java
@@ -0,0 +1,65 @@
+// MultivariateFunction.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.math;
+
+
+/**
+ * interface for a function of several variables
+ *
+ * @author Korbinian Strimmer
+ */
+public interface MultivariateFunction
+{
+ /**
+ * compute function value
+ *
+ * @param argument function argument (vector)
+ *
+ * @return function value
+ */
+ double evaluate(double[] argument);
+
+
+ /**
+ * get number of arguments
+ *
+ * @return number of arguments
+ */
+ int getNumArguments();
+
+ /**
+ * get lower bound of argument n
+ *
+ * @param n argument number
+ *
+ * @return lower bound
+ */
+ double getLowerBound(int n);
+
+ /**
+ * get upper bound of argument n
+ *
+ * @param n argument number
+ *
+ * @return upper bound
+ */
+ double getUpperBound(int n);
+
+ /**
+ * @return an Orthogonal Hints object that can be used by Orthogonal based optimisers
+ * to get information about the function
+ * @return if no such information just return null!
+ */
+ OrthogonalHints getOrthogonalHints();
+
+
+}
diff --git a/src/jebl/math/MultivariateMinimum.java b/src/jebl/math/MultivariateMinimum.java
new file mode 100644
index 0000000..5fe2e5e
--- /dev/null
+++ b/src/jebl/math/MultivariateMinimum.java
@@ -0,0 +1,259 @@
+// MultivariateMinimum.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * abstract base class for minimisation of a multivariate function
+ *
+ * @author Korbinian Strimmer
+ */
+public abstract class MultivariateMinimum
+{
+ //
+ // Public stuff
+ //
+
+ /** total number of function evaluations necessary */
+ public int numFun;
+
+ /**
+ * maxFun is the maximum number of calls to fun allowed.
+ * the default value of 0 indicates no limit on the number
+ * of calls.
+ */
+ public int maxFun = 0;
+
+ /**
+ * numFuncStops is the number of consecutive positive
+ * evaluations of the stop criterion based on function evaluation
+ * necessary to cause the abortion of the optimization
+ * (default is 4)
+ */
+ public int numFuncStops = 4;
+
+
+ /**
+ * Find minimum close to vector x
+ *
+ * @param f multivariate function
+ * @param xvec initial guesses for the minimum
+ * (contains the location of the minimum on return)
+ *
+ * @return minimal function value
+ */
+ public double findMinimum(MultivariateFunction f, double[] xvec)
+ {
+ optimize(f, xvec, MachineAccuracy.EPSILON, MachineAccuracy.EPSILON);
+
+ return f.evaluate(xvec);
+ }
+ /**
+ * Find minimum close to vector x
+ * (desired fractional digits for each parameter is specified)
+ *
+ * @param f multivariate function
+ * @param xvec initial guesses for the minimum
+ * (contains the location of the minimum on return)
+ * @param fxFracDigits desired fractional digits in the function value
+ * @param xFracDigits desired fractional digits in parameters x
+ *
+ * @return minimal function value
+ */
+ public double findMinimum(MultivariateFunction f, double[] xvec,
+ int fxFracDigits, int xFracDigits)
+ {
+ return findMinimum(f,xvec,fxFracDigits,xFracDigits,null);
+ }
+ /**
+ * Find minimum close to vector x
+ * (desired fractional digits for each parameter is specified)
+ *
+ * @param f multivariate function
+ * @param xvec initial guesses for the minimum
+ * (contains the location of the minimum on return)
+ * @param fxFracDigits desired fractional digits in the function value
+ * @param xFracDigits desired fractional digits in parameters x
+ *
+ * @return minimal function value
+ */
+ public double findMinimum(MultivariateFunction f, double[] xvec,
+ int fxFracDigits, int xFracDigits, MinimiserMonitor monitor)
+ {
+ double tolfx = Math.pow(10, -1-fxFracDigits);
+ double tolx = Math.pow(10, -1-xFracDigits);
+
+ optimize(f, xvec, tolfx, tolx,monitor);
+ // trim x
+ double m = Math.pow(10, xFracDigits);
+ for (int i = 0; i < xvec.length; i++)
+ {
+ xvec[i] = Math.round(xvec[i]*m)/m;
+ }
+ // trim fx
+ return Math.round(f.evaluate(xvec)*m)/m;
+ }
+
+ /**
+ * The actual optimization routine
+ * (needs to be implemented in a subclass of MultivariateMinimum).
+ * It finds a minimum close to vector x when the
+ * absolute tolerance for each parameter is specified.
+ *
+ * @param f multivariate function
+ * @param xvec initial guesses for the minimum
+ * (contains the location of the minimum on return)
+ * @param tolfx absolute tolerance of function value
+ * @param tolx absolute tolerance of each parameter
+ */
+ public abstract void optimize(MultivariateFunction f, double[] xvec, double tolfx, double tolx);
+
+ /**
+ * The actual optimization routine
+ *
+ * It finds a minimum close to vector x when the
+ * absolute tolerance for each parameter is specified.
+ *
+ * @param f multivariate function
+ * @param xvec initial guesses for the minimum
+ * (contains the location of the minimum on return)
+ * @param tolfx absolute tolerance of function value
+ * @param tolx absolute tolerance of each parameter
+ * @param monitor A monitor object that receives information about the minimising process (for display purposes)
+ * note: The default implementation just calls the optimize function with out the Monitor!
+ */
+
+ public void optimize(MultivariateFunction f, double[] xvec, double tolfx, double tolx, MinimiserMonitor monitor) {
+ optimize(f,xvec,tolfx,tolx);
+ }
+ /**
+ * Checks whether optimization should stop
+ *
+ * @param fx current function value
+ * @param x current values of function parameters
+ * @param tolfx absolute tolerance of function value
+ * @param tolx absolute tolerance of each parameter
+ * @param firstCall needs to be set to true when this routine is first called
+ * otherwise it should be set to false
+ *
+ * @return true if either x and its previous value are sufficiently similar
+ * or if fx and its previous values are sufficiently similar
+ * (test on function value has to be succesful numFuncStops consecutive
+ * times)
+ */
+ public boolean stopCondition(double fx, double[] x, double tolfx,
+ double tolx, boolean firstCall)
+ {
+ boolean stop = false;
+
+ if (firstCall)
+ {
+ countFuncStops = 0;
+ fxold = fx;
+ xold = new double[x.length];
+ copy(xold, x);
+ }
+ else
+ {
+ if (xStop(x, xold, tolx))
+ {
+ stop = true;
+ }
+ else
+ {
+ if (fxStop(fx, fxold, tolfx))
+ {
+ countFuncStops++;
+ }
+ else
+ {
+ countFuncStops = 0;
+ }
+
+ if (countFuncStops >= numFuncStops)
+ {
+ stop = true;
+ }
+ }
+ }
+
+ if (!stop)
+ {
+ fxold = fx;
+ copy(xold, x);
+ }
+
+ return stop;
+ }
+
+
+ /**
+ * Copy source vector into target vector
+ *
+ * @param target parameter array
+ * @param source parameter array
+ */
+ public static final void copy(final double[] target, final double[] source)
+ {
+ System.arraycopy(source,0,target,0,source.length);
+ }
+
+ //
+ // Private stuff
+ //
+
+ // number of fStops
+ private int countFuncStops;
+
+ // old function and parameter values
+ private double fxold;
+ private double[] xold;
+
+ private boolean xStop(double[] x, double[] xold, double tolx)
+ {
+ boolean stop = true;
+
+ for (int i = 0; i < x.length && stop == true; i++)
+ {
+ if (Math.abs(x[i]-xold[i]) > tolx)
+ {
+ stop = false;
+ }
+ }
+
+ return stop;
+ }
+
+ private boolean fxStop(double fx, double fxold, double tolfx)
+ {
+ if (Math.abs(fx-fxold) > tolfx)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+// ===========================================================================
+// ==== Factory interface
+ /**
+ * A factory interface for MultivariateMinimums (because they aren't statefree)
+ */
+ public static interface Factory {
+ /**
+ * Generate a new Multivariate Minimum
+ */
+ MultivariateMinimum generateNewMinimiser();
+ }
+}
diff --git a/src/jebl/math/NumericalDerivative.java b/src/jebl/math/NumericalDerivative.java
new file mode 100644
index 0000000..2992a6f
--- /dev/null
+++ b/src/jebl/math/NumericalDerivative.java
@@ -0,0 +1,140 @@
+// NumericalDerivative.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+// Known bugs and limitations:
+// - the sparse number of function evaluations used can potentially
+// lead to strong inaccuracies if the function is ill-behaved
+
+
+package jebl.math;
+
+
+/**
+ * approximates numerically the first and second derivatives of a
+ * function of a single variable and approximates gradient and
+ * diagonal of Hessian for multivariate functions
+ *
+ * @author Korbinian Strimmer
+ */
+public class NumericalDerivative
+{
+ //
+ // Public stuff
+ //
+
+
+ /**
+ * determine first derivative
+ *
+ * @param f univariate function
+ * @param x argument
+ *
+ * @return first derivate at x
+ */
+ public static double firstDerivative(UnivariateFunction f, double x)
+ {
+ double h = MachineAccuracy.SQRT_EPSILON*(Math.abs(x) + 1.0);
+
+ // Centered first derivative
+ return (f.evaluate(x + h) - f.evaluate(x - h))/(2.0*h);
+ }
+
+ /**
+ * determine second derivative
+ *
+ * @param f univariate function
+ * @param x argument
+ *
+ * @return second derivate at x
+ */
+ public static double secondDerivative(UnivariateFunction f, double x)
+ {
+ double h = MachineAccuracy.SQRT_SQRT_EPSILON*(Math.abs(x) + 1.0);
+
+ // Centered second derivative
+ return (f.evaluate(x + h) - 2.0*f.evaluate(x) + f.evaluate(x - h))/(h*h);
+ }
+
+
+ /**
+ * determine gradient
+ *
+ * @param f multivariate function
+ * @param x argument vector
+ *
+ * @return gradient at x
+ */
+ public static double[] gradient(MultivariateFunction f, double[] x)
+ {
+ double[] result = new double[x.length];
+
+ gradient(f, x, result);
+
+ return result;
+ }
+
+ /**
+ * determine gradient
+ *
+ * @param f multivariate function
+ * @param x argument vector
+ * @param grad vector for gradient
+ */
+ public static void gradient(MultivariateFunction f, double[] x, double[] grad)
+ {
+ for (int i = 0; i < f.getNumArguments(); i++)
+ {
+ double h = MachineAccuracy.SQRT_EPSILON*(Math.abs(x[i]) + 1.0);
+
+ double oldx = x[i];
+ x[i] = oldx + h;
+ double fxplus = f.evaluate(x);
+ x[i] = oldx - h;
+ double fxminus = f.evaluate(x);
+ x[i] = oldx;
+
+ // Centered first derivative
+ grad[i] = (fxplus-fxminus)/(2.0*h);
+ }
+ }
+
+ /**
+ * determine diagonal of Hessian
+ *
+ * @param f multivariate function
+ * @param x argument vector
+ *
+ * @return vector with diagonal entries of Hessian
+ */
+ public static double[] diagonalHessian(MultivariateFunction f, double[] x)
+ {
+ int len = f.getNumArguments();
+ double[] result = new double[len];
+
+ for (int i = 0; i < len; i++)
+ {
+ double h = MachineAccuracy.SQRT_SQRT_EPSILON*(Math.abs(x[i]) + 1.0);
+
+ double oldx = x[i];
+ x[i] = oldx + h;
+ double fxplus = f.evaluate(x);
+ x[i] = oldx - h;
+ double fxminus = f.evaluate(x);
+ x[i] = oldx;
+ double fx = f.evaluate(x);
+
+ // Centered second derivative
+ result[i] = (fxplus - 2.0*fx + fxminus)/(h*h);
+ }
+
+ return result;
+ }
+}
diff --git a/src/jebl/math/OrderEnumerator.java b/src/jebl/math/OrderEnumerator.java
new file mode 100644
index 0000000..240de9e
--- /dev/null
+++ b/src/jebl/math/OrderEnumerator.java
@@ -0,0 +1,443 @@
+// OrderEnumerator.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2002 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.math;
+
+
+/**
+ * A means for describing odering information, and Utilities for creating such Orderings
+ *
+ * @version $Id: OrderEnumerator.java 258 2006-03-16 23:45:00Z twobeers $
+ *
+ * @author Matthew Goode
+ */
+
+public interface OrderEnumerator {
+ /**
+ * If hasMore returns false reset should be called
+ */
+ boolean hasMore();
+ /**
+ * The next value in the enumeration
+ */
+ int getNext();
+ /**
+ * Reset back to starting state, may have a differnet number of values, and a different ordering after a reset!
+ */
+ void reset();
+
+ public static interface OEFactory {
+ /**
+ * For generating an ordering from 0..size-1. Enumerator doesn't have to actually produce
+ */
+ public OrderEnumerator createOrderEnumerator(int size);
+ }
+
+//=====================================================================================================
+//================================= Utilities, and hidden classes =====================================
+//=====================================================================================================
+
+ public static class Utils {
+ private static final Constant ZERO = new Constant(0);
+ /**
+ * @param index The index to always return
+ * @return an OrderEnumerator object that always returns 'index'
+ */
+ public static final OrderEnumerator getConstant(int index) {
+ return new Constant(index);
+ }
+
+ /**
+ * @param size the number of different indexes returned (between 0 and size-1)
+ * @return an OrderEnumerator object returns index in order between a certain range
+ */
+ public static final OrderEnumerator getOrdered(int size) {
+ return new Ordered(size);
+ }
+ /**
+ * @param size the number of different indexes returned (between 0 and size-1)
+ * @return an OrderEnumerator object returns index in random order between a certain range (order changes with each reset)
+ */
+ public static final OrderEnumerator getShuffled(int size) {
+ return new Shuffled(size);
+ }
+ /**
+ * @param primary The primary OrderEnumerator, one index is taken from this enumertor than an entire sequence of the secondary is taken
+ * @param secondary The primary OrderEnumerator, the entire sequence of a secondary enumerator is taken for every single index from the primary enumerator
+ *
+ * @return an OrderEnumerator object that combines two sub enumerators
+ */
+ public static final OrderEnumerator getBiasAlternating(OrderEnumerator primary, OrderEnumerator secondary) {
+ return new BiasAlternate(primary,secondary);
+ }
+ /**
+ * @param primary The primary OrderEnumerator
+ * @param secondary The primary OrderEnumerator
+ *
+ * @return an OrderEnumerator object that combines two sub enumerators, by alternating between outputs
+ */
+ public static final OrderEnumerator getAlternating(OrderEnumerator primary, OrderEnumerator secondary) {
+ return new Alternate(primary,secondary);
+ }
+ /**
+ * @return OrderEnumerator that always returns 0 (zero)
+ */
+ public static final OrderEnumerator getZero() {
+ return ZERO;
+ }
+ /**
+ * @param minimum minmim value released
+ * @param range range of values released (that is values go between minimum (inclusive) and minimum+range(exclusive)
+ *
+ * @return an OrderEnumerator that is restricted in indexes it returns based on base Enumerator
+ *
+ */
+ public static final OrderEnumerator getRestricted(OrderEnumerator toRestrict, int minimum, int range) {
+ return new Restricted(toRestrict,minimum, range);
+ }
+
+ /**
+ * @return OrderEnumerator that always returns 0 (zero)
+ */
+ public static final OrderEnumerator getAdjusted(OrderEnumerator toAdjust, int adjustmentFactor) {
+ return new Adjust(toAdjust,adjustmentFactor);
+ }
+
+ //============================================================
+ //=================== Factory Stuff ==========================
+ /**
+ * @return OrderEnumerator that always returns 0 (zero)
+ */
+ public static final OrderEnumerator.OEFactory getZeroFactory() {
+ return ZERO;
+ }
+ /**
+ * @param index The index to always return
+ * @return an OrderEnumerator object that always returns 'index'
+ */
+ public static final OrderEnumerator.OEFactory getConstantFactory(int index) {
+ return new Constant(index);
+ }
+
+ /**
+ * @return an OrderEnumerator object returns index in order between a certain range
+ */
+ public static final OrderEnumerator.OEFactory getOrderedFactory() {
+ return Ordered.Factory.INSTANCE;
+ }
+ /**
+ * @return an OrderEnumerator object returns index in random order between a certain range (order changes with each reset)
+ */
+ public static final OrderEnumerator.OEFactory getShuffledFactory() {
+ return Shuffled.Factory.INSTANCE;
+ }
+ /**
+ * @param adjustmentFactor If to adjust returns x, adjusted will return x+adjustmentFactory (it's that simple)
+ * @return an OrderEnumerator that returns indexes adjusted from a base enumerator
+ *
+ */
+ public static final OrderEnumerator.OEFactory getAdjustedFactory(OrderEnumerator.OEFactory toAdjust, int adjustmentFactor) {
+ return new Adjust.Factory(toAdjust,adjustmentFactor);
+ }
+ /**
+ * @param minimum minmim value released
+ * @param range range of values released (that is values go between minimum (inclusive) and minimum+range(exclusive)
+ *
+ * @return an OrderEnumerator that is restricted in indexes it returns based on base Enumerator
+ *
+ */
+ public static final OrderEnumerator.OEFactory getRestrictedFactory(OrderEnumerator.OEFactory toRestrict, int minimum, int range) {
+ return new Restricted.Factory(toRestrict,minimum, range);
+ }
+
+ /**
+ * @return an OrderEnumerator object that alternates outputs between two base enumerator
+ */
+ public static final OrderEnumerator.OEFactory getAlternatingFactory(OrderEnumerator.OEFactory primary, OrderEnumerator.OEFactory secondary) {
+ return new Alternate.Factory(primary,secondary);
+ }
+ /**
+ * @return an OrderEnumerator object that alternates outputs between two base enumerator
+ * (takes one from primary, than all from secondary, one from primary, all from secondary)
+ */
+ public static final OrderEnumerator.OEFactory getBiasAlternatingFactory(OrderEnumerator.OEFactory primary, OrderEnumerator.OEFactory secondary) {
+ return new BiasAlternate.Factory(primary,secondary);
+ }
+ //=======================================================
+
+ /**
+ * Returns the same index ever call
+ */
+ private static class Constant implements OrderEnumerator, OrderEnumerator.OEFactory {
+ int index_;
+ boolean hasMore_;
+ public Constant(int index) {
+ this.index_ = index;
+ }
+ public boolean hasMore() { return hasMore_; }
+ public int getNext() { hasMore_ = false; return index_; }
+ public void reset() { hasMore_ = true; }
+ /**
+ * For generating an ordering from 0..size-1. Enumerator doesn't have to actually produce
+ */
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return this;
+ }
+
+ } //End of Constant
+ //=======================================================
+
+ /**
+ * Incrementally returns indexes
+ */
+ private static class Ordered implements OrderEnumerator {
+ int index_;
+ int size_;
+ public Ordered(int size) {
+ this.size_= size;
+ reset();
+ }
+ public boolean hasMore() {
+ return index_<size_;
+ }
+ public int getNext() { return index_++; }
+ public void reset() { index_ = 0; }
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ static final Factory INSTANCE = new Factory();
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new Ordered(size);
+ }
+ } //End of Ordered.Factory
+ } //End of Ordered
+ //=======================================================
+ /**
+ * Returns indexes between 0 and size but in a shuffled order. Order changes with each reset.
+ */
+ private static class Shuffled implements OrderEnumerator {
+ int currentIndex_;
+ int[] indexes_;
+ int size_;
+ MersenneTwisterFast random_ = new MersenneTwisterFast();
+ public Shuffled(int size) {
+ this.size_= size;
+ this.indexes_ = new int[size];
+ for(int i = 0 ; i < size ; i++) {
+ indexes_[i] = i;
+ }
+ reset();
+ }
+ public boolean hasMore() {
+ return currentIndex_<size_;
+ }
+ public int getNext() { return indexes_[currentIndex_++]; }
+ public void reset() { currentIndex_ = 0; random_.shuffle(indexes_); }
+
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ static final Factory INSTANCE = new Factory();
+
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new Shuffled(size);
+ }
+ } //End of Shuffled.Factory
+ } //End of Shuffled
+ //=======================================================
+ /**
+ * Restricts indexes output by a sub OrderEnumerator by to between a certain range,
+ * skips over indexes outside range
+ */
+ private static class Restricted implements OrderEnumerator {
+ OrderEnumerator toAdjust_;
+ int minimum_;
+ int top_;
+ int next_;
+ boolean hasMore_;
+
+ public Restricted(OrderEnumerator toAdjust, int minimum, int range) {
+ this.toAdjust_ = toAdjust;
+ this.minimum_ = minimum;
+ this.top_ = minimum+range;
+ reset();
+ }
+ public boolean hasMore() {
+ return hasMore_;
+ }
+ private void updateNext() {
+ hasMore_ = false;
+ while(toAdjust_.hasMore()) {
+ int i = toAdjust_.getNext();
+ if(i>=minimum_&&i<top_) {
+ next_ = i;
+ hasMore_ = true;
+ break;
+ }
+ }
+ }
+ public int getNext() { int myNext = next_; updateNext(); return myNext; }
+ public void reset() { toAdjust_.reset(); updateNext(); }
+
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ OrderEnumerator.OEFactory toAdjustFactory_;
+ int minimum_, range_;
+ public Factory(OrderEnumerator.OEFactory toAdjustFactory, int minimum, int range) {
+ this.toAdjustFactory_ = toAdjustFactory;
+ this.minimum_ = minimum;
+ this.range_ = range;
+ }
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new Restricted(toAdjustFactory_.createOrderEnumerator(size),minimum_,range_);
+ }
+ } //End of Restricted.Factory
+ } //End of Restricted
+ /**
+ * Alters indexes output by a sub OrderEnumerator by constant amount
+ */
+ private static class Adjust implements OrderEnumerator {
+ OrderEnumerator toAdjust_;
+ int adjustmentAmount_;
+ /**
+ * @param adjustmentAmount - how much adjust a value by, should be positive
+ * (for example adjustment of 1 will mean when sub returns 5, this will return 6)
+ */
+ public Adjust(OrderEnumerator toAdjust, int adjustmentAmount) {
+ this.toAdjust_ = toAdjust;
+ this.adjustmentAmount_ = adjustmentAmount;
+ reset();
+ }
+ public boolean hasMore() {
+ return toAdjust_.hasMore();
+ }
+ public int getNext() { return toAdjust_.getNext()+adjustmentAmount_; }
+ public void reset() { toAdjust_.reset(); }
+
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ OrderEnumerator.OEFactory toAdjustFactory_;
+ int adjustmentAmount_;
+ public Factory(OrderEnumerator.OEFactory toAdjustFactory, int adjustmentAmount) {
+ this.toAdjustFactory_ = toAdjustFactory;
+ this.adjustmentAmount_ = adjustmentAmount;
+ }
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new Adjust(toAdjustFactory_.createOrderEnumerator(size-adjustmentAmount_),adjustmentAmount_);
+ }
+ } //End of Adjust.Factory
+ } //End of Adjust
+
+ //=======================================================
+ /**
+ * Returns alternating between each enumerator.
+ */
+ private static class Alternate implements OrderEnumerator {
+ OrderEnumerator primary_;
+ OrderEnumerator secondary_;
+ boolean onSecondary_; //Only have this instead of just using secondary_.hasMore() for very first iteration!
+ /**
+ * Alternates from each untill both used up
+ */
+ public Alternate(OrderEnumerator primary, OrderEnumerator secondary) {
+ this.primary_ = primary;
+ this.secondary_ = secondary;
+ reset();
+ }
+ public boolean hasMore() {
+ return primary_.hasMore()||secondary_.hasMore();
+ }
+ public int getNext() {
+ int toReturn;
+ if(onSecondary_&&secondary_.hasMore()) {
+ toReturn = secondary_.getNext();
+ onSecondary_ = !primary_.hasMore();
+ } else {
+ toReturn = primary_.getNext();
+ onSecondary_ = secondary_.hasMore();
+ return toReturn;
+ }
+ return toReturn;
+ }
+ public void reset() {
+ primary_.reset();
+ secondary_.reset();
+ onSecondary_ = false;
+ }
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ OrderEnumerator.OEFactory primary_;
+ OrderEnumerator.OEFactory secondary_;
+ public Factory(OrderEnumerator.OEFactory primary, OrderEnumerator.OEFactory secondary) {
+ this.primary_ = primary;
+ this.secondary_ = secondary;
+ }
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new Alternate(primary_.createOrderEnumerator(size),secondary_.createOrderEnumerator(size));
+ }
+ } //End of Atlernate.Factory
+ } //End of Alternate
+
+ //=======================================================
+ /**
+ * Returns indexes between 0 and size but in a shuffled order. Order changes with each reset.
+ */
+ private static class BiasAlternate implements OrderEnumerator {
+ OrderEnumerator primary_;
+ OrderEnumerator secondary_;
+ boolean onSecondary_; //Only have this instead of just using secondary_.hasMore() for very first iteration!
+ /**
+ * For every index taken from primary, an entire round of secondary will be taken
+ */
+ public BiasAlternate(OrderEnumerator primary, OrderEnumerator secondary) {
+ this.primary_ = primary;
+ this.secondary_ = secondary;
+ reset();
+ }
+
+ public boolean hasMore() {
+ return(primary_.hasMore()||(onSecondary_&&secondary_.hasMore()));
+ }
+ public int getNext() {
+ if(onSecondary_) {
+ int toReturn = secondary_.getNext();
+ onSecondary_ = secondary_.hasMore();
+ return toReturn;
+ } else {
+ secondary_.reset();
+ onSecondary_ = secondary_.hasMore();
+ return primary_.getNext();
+ }
+ }
+ public void reset() {
+ primary_.reset();
+ onSecondary_ = false;
+ }
+ //=====================================================
+ //================= Factory ===========================
+ static class Factory implements OrderEnumerator.OEFactory {
+ OrderEnumerator.OEFactory primary_;
+ OrderEnumerator.OEFactory secondary_;
+ public Factory(OrderEnumerator.OEFactory primary, OrderEnumerator.OEFactory secondary) {
+ this.primary_ = primary;
+ this.secondary_ = secondary;
+ }
+ public OrderEnumerator createOrderEnumerator(int size) {
+ return new BiasAlternate(primary_.createOrderEnumerator(size),secondary_.createOrderEnumerator(size));
+ }
+ } //End of BiasAlternate.Factory
+ } //End of BiasAlternate
+
+ } //End of Utils
+}
\ No newline at end of file
diff --git a/src/jebl/math/OrthogonalHints.java b/src/jebl/math/OrthogonalHints.java
new file mode 100644
index 0000000..20d698e
--- /dev/null
+++ b/src/jebl/math/OrthogonalHints.java
@@ -0,0 +1,201 @@
+// OrthogonalHints.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.math;
+
+/**
+ * Provides a means for giving an Orthogonal base optimiser (IE, OrthognalMinimum)
+ * hints about the function that may alow it to optimise better.
+ *
+ * @version $Id: OrthogonalHints.java 258 2006-03-16 23:45:00Z twobeers $
+ *
+ * @author Matthew Goode
+ */
+
+public interface OrthogonalHints {
+ /**
+ * If there is a "best" ordering to use it can be specified here,
+ * if not should return null
+ * @param defaultOrdering The ordering suggested by the optimiser, may be null!
+ * @return null, or default ordering if no known best ordering
+ */
+ public OrderEnumerator getSuggestedOrdering(OrderEnumerator defaultOrdering);
+ /**
+ * A boundary is a value of a parameter for which values lower than the boundary and values
+ * higher than the boundary are better treated as two separate functions (IE, they
+ * are only piecewise connected), and minimisation should be performed over both ranges
+ * individually (and then the true minimum taken as the minimuma of the ranges)
+ * @return the number of boundary locations stored in storage, or -1 if not enough
+ * room, or 0 if there are no boundaries (other than the normal parameter range)
+ */
+ public int getInternalParameterBoundaries(int parameter, double[] storage);
+
+//=====================================================================================================
+//================================= Utilities, and hidden classes =====================================
+//=====================================================================================================
+
+ public static class Utils {
+ /**
+ * @return a new OrthogonalHints object base on toAdjust that works with parameters from adjustmentFactor + what toAdjust worked with
+ * That is if the value x is the parameter will be passed toAdjust as x-adjustmentFactor, and
+ * the suggested OrderEnumerator adjusts input x by adding adjustment factor before returning to
+ * the sub toAdjust Enumerator (if you know what I mean)
+ */
+ public final static OrthogonalHints getAdjusted(OrthogonalHints toAdjust, int adjustmentFactor) {
+ return new Adjusted(toAdjust,adjustmentFactor);
+ }
+ /**
+ * @return a new OrthogonalHints object that combines two sub OrthogonalHints objects so that
+ * all parameter information between 0 upto (but not including) numberOfFirstParameters is
+ * passed to first, and everything else is passed to second
+ * note: automatically adjusts second so assumes both first and second handle parameters in
+ * range 0..whatever (do not do preadjusment on second!)
+ */
+ public final static OrthogonalHints getCombined(OrthogonalHints first, int numberOfFirstParameters, OrthogonalHints second, int numberOfSecondParameters) {
+ return new Combined(first,numberOfFirstParameters,second,numberOfSecondParameters);
+ }
+
+ public final static double[] getInternalParameterBoundaries(OrthogonalHints base, int parameter) {
+ double[] store = new double[100];
+
+ int numberReturned = base.getInternalParameterBoundaries(parameter,store);
+ while(numberReturned<0) {
+ store = new double[store.length+10];
+ numberReturned = base.getInternalParameterBoundaries(parameter,store);
+ }
+ double[] result = new double[numberReturned];
+ System.arraycopy(store,0,result,0,numberReturned);
+ return result;
+ }
+
+ /**
+ * @return an OrthogonalHints object that doesn't provide any hints
+ */
+ public final static OrthogonalHints getNull() {
+ return Null.INSTANCE;
+ }
+
+ // =======================================================================
+ /**
+ * Implements a means for adjusting an orthogonal hints (that is introduce a simple
+ * mapping between given parameter indexes and used parameter indexes)
+ */
+ private final static class Adjusted implements OrthogonalHints {
+ OrthogonalHints toAdjust_;
+ int adjustmentFactor_;
+ public Adjusted(OrthogonalHints toAdjust, int adjustmentFactor) {
+ this.toAdjust_ = toAdjust;
+ this.adjustmentFactor_ = adjustmentFactor;
+ }
+ public OrderEnumerator getSuggestedOrdering(OrderEnumerator defaultOrdering) {
+ OrderEnumerator sub = toAdjust_.getSuggestedOrdering(defaultOrdering);
+ if(sub==null||sub==defaultOrdering) {
+ return defaultOrdering;
+ }
+ return OrderEnumerator.Utils.getAdjusted(sub,adjustmentFactor_);
+ }
+ public int getInternalParameterBoundaries(int parameter, double[] storage) {
+ return toAdjust_.getInternalParameterBoundaries(parameter-adjustmentFactor_,storage);
+ }
+ } //End of Adjusted
+
+ // =======================================================================
+ /**
+ * An OrthogonalHints object that provides no hints!
+ */
+ private final static class Null implements OrthogonalHints {
+ public static final Null INSTANCE = new Null();
+ public Null() { }
+ public OrderEnumerator getSuggestedOrdering(OrderEnumerator defaultOrdering) {
+ return defaultOrdering;
+ }
+ public int getInternalParameterBoundaries(int parameter, double[] storage) {
+ return 0;
+ }
+ } //End of Null
+
+ // =======================================================================
+ /**
+ * Implements a means for combining two OrthogonalHints objects
+ */
+ private final static class Combined implements OrthogonalHints {
+ OrthogonalHints hintsOne_,hintsTwo_;
+ int hintOneParameterCount_, hintTwoParameterCount_ ;
+ /**
+ * @param hintOneParameterCount The number of parameters handled by hintsOne
+ * @param hintTwoParameterCount The number of parameters handled by hintsTwo
+ *
+ */
+ public Combined(OrthogonalHints hintsOne, int hintOneParameterCount, OrthogonalHints hintsTwo, int hintTwoParameterCount) {
+ this.hintsOne_ = hintsOne;
+ this.hintOneParameterCount_ = hintOneParameterCount;
+ this.hintTwoParameterCount_ = hintTwoParameterCount;
+
+ this.hintsTwo_ = hintsTwo;
+ }
+ /**
+ * if no suggested ordering from either sub hints returns null, if only
+ * one hint has suggested ordering, creates an ordering where those parameters belonging to
+ * the respecitive hint are given by the given ordering and the remaining ordering information
+ * is shuffled.
+ */
+ public OrderEnumerator getSuggestedOrdering(OrderEnumerator defaultOrdering) {
+ OrderEnumerator oe1 = hintsOne_.getSuggestedOrdering(null);
+ OrderEnumerator oe2 = hintsTwo_.getSuggestedOrdering(null);
+ if(oe1==null&&oe2==null) {
+ return defaultOrdering;
+ }
+ if(oe1==null&&oe2!=null) {
+ if(defaultOrdering!=null) {
+ return OrderEnumerator.Utils.getAlternating(
+ OrderEnumerator.Utils.getRestricted(defaultOrdering,0,hintOneParameterCount_),
+ OrderEnumerator.Utils.getAdjusted(oe2,hintOneParameterCount_)
+ );
+
+ }
+ return OrderEnumerator.Utils.getAlternating(
+ OrderEnumerator.Utils.getShuffled(hintOneParameterCount_),
+ OrderEnumerator.Utils.getAdjusted(oe2,hintOneParameterCount_)
+ );
+ }
+ if(oe2==null) {
+ if(defaultOrdering!=null) {
+ return OrderEnumerator.Utils.getAlternating(
+ oe1,
+ OrderEnumerator.Utils.getRestricted(
+ defaultOrdering,
+ hintOneParameterCount_,hintOneParameterCount_+hintTwoParameterCount_
+ )
+ );
+ }
+ return OrderEnumerator.Utils.getAlternating(
+ oe1,
+ OrderEnumerator.Utils.getAdjusted(
+ OrderEnumerator.Utils.getShuffled(hintTwoParameterCount_),
+ hintOneParameterCount_
+ )
+ );
+ }
+ return OrderEnumerator.Utils.getAlternating(oe1,OrderEnumerator.Utils.getAdjusted(oe2,hintOneParameterCount_));
+ }
+ /**
+
+ */
+ public int getInternalParameterBoundaries(int parameter, double[] storage) {
+ if(parameter<hintOneParameterCount_) {
+ return hintsOne_.getInternalParameterBoundaries(parameter,storage);
+ }
+ return hintsTwo_.getInternalParameterBoundaries(parameter-hintOneParameterCount_,storage);
+ }
+ } //End of Combined
+ } //End of Utils
+
+}
\ No newline at end of file
diff --git a/src/jebl/math/OrthogonalLineFunction.java b/src/jebl/math/OrthogonalLineFunction.java
new file mode 100644
index 0000000..32e5ad5
--- /dev/null
+++ b/src/jebl/math/OrthogonalLineFunction.java
@@ -0,0 +1,122 @@
+// OrthogonalLineFunction.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * converts a multivariate function into a univariate function
+ * by keeping all but one argument constant
+ *
+ * @author Korbinian Strimmer
+ */
+public class OrthogonalLineFunction implements UnivariateFunction
+{
+ /**
+ * construct univariate function from multivariate function
+ *
+ * @param func multivariate function
+ */
+ public OrthogonalLineFunction(MultivariateFunction func) {
+ this(func, 0, null);
+ }
+ /**
+ * construct univariate function from multivariate function
+ *
+ *
+ * @param func multivariate function
+ * @param selectedDimension The selected dimension/argument that the line "runs" along
+ * @param initialArguments the initial arguments to the base MultivariateFunction (may be null)
+ */
+ public OrthogonalLineFunction(MultivariateFunction func, int selectedDimension, double[] initialArguments ) {
+ f = func;
+ numArgs = f.getNumArguments();
+ x = new double[numArgs];
+
+ this.n = selectedDimension;
+ if(initialArguments!=null) {
+ System.arraycopy(initialArguments,0,x,0,Math.min(x.length,initialArguments.length));
+ }
+ }
+ /**
+ * set (change) values of all arguments (start values)
+ *
+ * @param start start values
+ */
+ public void setAllArguments(double[] start)
+ {
+ for (int i = 0; i < numArgs; i++)
+ {
+ x[i] = start[i];
+ }
+ }
+
+ /**
+ * set (change) value of a single argument
+ * (the one currently active)
+ *
+ * @param val value of argument
+ */
+ public void setArgument(double val)
+ {
+ x[n] = val;
+ bak = x[n];
+ }
+
+
+ /**
+ * use only the specified argument in the
+ * constructed univariate function
+ * and keep all others constant
+ *
+ * @param num argument number
+ */
+ public void selectArgument(int num)
+ {
+ n = num;
+ bak = x[n];
+ if(f.getLowerBound(num) == f.getUpperBound(num)){
+ System.out.println("Warning! Range is zero on parameter:"+num);
+ }
+ }
+
+ // implementation of UnivariateFunction
+
+ public double evaluate(double arg)
+ {
+ x[n] = arg;
+ double v = f.evaluate(x);
+ x[n] = bak;
+
+ return v;
+ }
+
+ public double getLowerBound()
+ {
+ return f.getLowerBound(n);
+ }
+
+ public double getUpperBound()
+ {
+ return f.getUpperBound(n);
+ }
+
+
+ //
+ // Private stuff
+ //
+
+ private MultivariateFunction f;
+ private int numArgs, n;
+ private double bak;
+ private double[] s, x;
+}
diff --git a/src/jebl/math/OrthogonalSearch.java b/src/jebl/math/OrthogonalSearch.java
new file mode 100644
index 0000000..793434e
--- /dev/null
+++ b/src/jebl/math/OrthogonalSearch.java
@@ -0,0 +1,318 @@
+// OrthogonalSearch.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * minimization of a real-valued function of
+ * several variables without using derivatives, using the simple
+ * strategy of optimizing variables one by one.
+ *
+ * @author Korbinian Strimmer
+ * @author Matthew Goode
+ */
+
+public class OrthogonalSearch extends MultivariateMinimum
+{
+ //
+ // Public stuff
+ //
+
+
+ //
+ // Private stuff
+ //
+ private OrderEnumerator.OEFactory orthogonalOrderingFactory_;
+
+ /** Use the current value of dimension in univariate minimisation, or ignore (original method) */
+ private boolean useCurrentInUnivariateMinimisation_ = false;
+ /** Sometimes the minimum gained through the single variate minimisation is
+ * worse than the minimum currently found (in that it has found another minimum
+ * which is not the original, and is not as minimumal).
+ * This can cause convergence problems, if this is true than the original minima
+ * will be kept if it is more minimal than the new minimuma. This ensures convergence.
+ * In the future a possible strategy might be SimulatedAnealing with regard to accepting,
+ * or rejecting new minima.
+ */
+ private boolean ignoreNonMinimalUnivariateMinimisations_ = true;
+
+ /**
+ * If true, print out debug info...
+ */
+ private boolean debug_ = false;
+
+ /**
+ * If true calls MinimiserMonitor methods after each orthogonal update, otherwise after each round
+ */
+ private boolean frequentMonitoring_ = true;
+
+ /**
+ * Initialization
+ */
+ public OrthogonalSearch() {
+ //this(OrderUtils.getBiasAlternatingFactory( OrderUtils.getOrderedFactory(), OrderUtils.getZeroFactory()));
+ this(OrderEnumerator.Utils.getOrderedFactory());
+ }
+ /**
+ * Initialization
+ * @param shuffle If true uses shuffling, else uses ascending order, when choosing next parameter to optimse
+ * (true means equivalent to old StochasticOSearch)
+ */
+ public OrthogonalSearch(boolean shuffle) {
+ //this(OrderUtils.getBiasAlternatingFactory( OrderUtils.getOrderedFactory(), OrderUtils.getZeroFactory()));
+ this(shuffle? OrderEnumerator.Utils.getShuffledFactory() : OrderEnumerator.Utils.getOrderedFactory());
+ }
+ /**
+ * Initialization
+ */
+ public OrthogonalSearch(OrderEnumerator.OEFactory orderingFactory) {
+ this.orthogonalOrderingFactory_ = orderingFactory;
+ }
+
+ /**
+ *
+ */
+ public void setUseCurrentInUnivariateMinimisation(boolean value) {
+ this.useCurrentInUnivariateMinimisation_ = value;
+ }
+ /**
+ * Should we ignore new minisations that are not as minimal as the current one?
+ */
+ public void setIgnoreNonMinimalUnivariateMinimisations(boolean value) {
+ this.ignoreNonMinimalUnivariateMinimisations_ = value;
+ }
+
+ // implementation of abstract method
+
+ public void optimize(MultivariateFunction f, double[] xvec, double tolfx, double tolx) {
+ optimize(f,xvec,tolfx,tolx,null);
+ }
+ public void optimize(MultivariateFunction f, double[] xvec, double tolfx, double tolx, MinimiserMonitor monitor) {
+ int numArgs = f.getNumArguments();
+
+ numFun = 1;
+ double fx = f.evaluate(xvec);
+
+ stopCondition(fx, xvec, tolfx, tolx, true);
+
+ RoundOptimiser od = generateOrthogonalRoundOptimiser(f);
+ UnivariateMinimum um = generateUnivariateMinimum();
+ double lastFX;
+ while (true) {
+ lastFX = fx;
+ fx = od.doRound(xvec,um,tolx,fx, (frequentMonitoring_ ? monitor : null));
+ if(monitor!=null) {
+ monitor.newMinimum(fx,xvec,f);
+ if (maxFun > 0) {
+ monitor.updateProgress((double) numFun / maxFun);
+ }
+ }
+ debug("Round fx:"+fx);
+
+ if (stopCondition(fx, xvec, tolfx, tolx, false) ||
+ (maxFun > 0 && numFun > maxFun) ||
+ numArgs == 1) {
+ break;
+ }
+ }
+ }
+
+ //============ Static Methods ====================
+
+ /**
+ * Generate a MultivariateMinimum.Factory for an OrthogonalSearch
+ * @param shuffle if true shuffles order for each round (see OrthogonalSearch constructors)
+ */
+ public static final Factory generateFactory(boolean shuffle) { return new SearchFactory(shuffle); }
+
+ //============ For sub classes ===================
+
+ protected UnivariateMinimum generateUnivariateMinimum() {
+ return new UnivariateMinimum();
+ }
+ protected boolean isFrequentMonitoring() {
+ return frequentMonitoring_;
+ }
+ protected RoundOptimiser generateOrthogonalRoundOptimiser(MultivariateFunction mf) {
+ OrthogonalHints hints = mf.getOrthogonalHints();
+ if(hints!=null) {
+ return new OrthogonalHintsDirection(mf,hints,orthogonalOrderingFactory_);
+ }
+ return new OrthogonalDirection(mf,orthogonalOrderingFactory_);
+ }
+
+ protected interface RoundOptimiser {
+ /**
+ * @param monitor - may be null;
+ */
+ public double doRound(double[] xvec, UnivariateMinimum um, double tolx,double fx, MinimiserMonitor monitor);
+ }
+
+ protected final boolean isUseCurrentInUnivariateMinimisation() {
+ return this.useCurrentInUnivariateMinimisation_;
+ }
+ /**
+ * Should we ignore new minisations that are not as minimal as the current one?
+ */
+ protected final boolean isIgnoreNonMinimalUnivariateMinimisations() {
+ return this.ignoreNonMinimalUnivariateMinimisations_;
+ }
+ protected void debug(Object output) {
+ if(debug_) {
+ System.out.println(output);
+ }
+ }
+ protected boolean isDebug() {
+ return debug_;
+ }
+ // ============ The Factory Class for Orthogonal Searches ===================
+ private static final class SearchFactory implements Factory {
+ boolean shuffle_;
+ private SearchFactory(boolean shuffle) { this.shuffle_ = shuffle; }
+ public MultivariateMinimum generateNewMinimiser() { return new OrthogonalSearch(shuffle_); }
+ }
+ //============== A means for doing Orthogonal optimisation ==================
+ private class OrthogonalDirection implements RoundOptimiser {
+ OrderEnumerator order_;
+ OrthogonalLineFunction olf_;
+ MultivariateFunction base_;
+
+ public OrthogonalDirection(MultivariateFunction mf, OrderEnumerator.OEFactory orderFactory) {
+ base_ = mf;
+ olf_ = new OrthogonalLineFunction(base_);
+ this.order_ = orthogonalOrderingFactory_.createOrderEnumerator(base_.getNumArguments());
+ }
+ public double doRound(double[] xvec, UnivariateMinimum um, double tolx,double fx, MinimiserMonitor monitor) {
+ olf_.setAllArguments(xvec);
+
+ order_.reset();
+ while(order_.hasMore()) {
+ int argument = order_.getNext();
+ olf_.selectArgument(argument);
+ double newArgValue =
+ (
+ useCurrentInUnivariateMinimisation_ ?
+ um.optimize(xvec[argument], olf_, tolx) :
+ um.optimize(olf_, tolx)
+ );
+ //If we actually found a better minimum...
+ if(um.fminx<=fx) {
+ xvec[argument] = newArgValue;
+ olf_.setArgument(newArgValue);
+ fx = um.fminx;
+ }
+ if(monitor!=null) {
+ monitor.newMinimum(fx,xvec,base_);
+ }
+ debug(argument+":"+um.fminx+" "+fx);
+ numFun += um.numFun;
+
+ }
+ return fx;
+ }
+ }
+ //============== A means for doing Orthogonal optimisation ==================
+ private class OrthogonalHintsDirection implements RoundOptimiser {
+ OrderEnumerator order_;
+ OrthogonalLineFunction olf_;
+ OrthogonalHints hints_;
+ double[] store_ = new double[100];
+ MultivariateFunction base_;
+ public OrthogonalHintsDirection(MultivariateFunction mf, OrthogonalHints hints, OrderEnumerator.OEFactory orderFactory) {
+ base_ = mf;
+ olf_ = new OrthogonalLineFunction(base_);
+
+ this.hints_ = hints;
+ this.order_ = orthogonalOrderingFactory_.createOrderEnumerator(base_.getNumArguments());
+ }
+ private final double getNormalMin(UnivariateMinimum um, double argumentValue, double tolx) {
+ return (
+ useCurrentInUnivariateMinimisation_ ?
+ um.optimize(argumentValue, olf_, tolx) :
+ um.optimize(olf_, tolx)
+ );
+ }
+ private final double getBoundedMin(UnivariateMinimum um, double argumentValue, double tolx,double min, double max) {
+ if(useCurrentInUnivariateMinimisation_ && (min<=argumentValue&&max>=argumentValue)) {
+ return um.optimize(argumentValue, olf_, tolx,min,max);
+ }
+ return um.optimize(olf_, tolx,min,max);
+ }
+
+
+ public double doRound(double[] xvec, UnivariateMinimum um, double tolx,double fx, MinimiserMonitor monitor) {
+ olf_.setAllArguments(xvec);
+
+ order_.reset();
+ while(order_.hasMore()) {
+ int argument = order_.getNext();
+ olf_.selectArgument(argument);
+ int numberOfHints= hints_.getInternalParameterBoundaries(argument,store_);
+ //Yes this expensive, but will not happen very often (and only at beging of optimisation)
+ while(numberOfHints<0) {
+ store_ = new double[store_.length+10];
+ numberOfHints= hints_.getInternalParameterBoundaries(argument,store_);
+ }
+ double newArgValue;
+ double newFX;
+ if(numberOfHints==0) {
+ newArgValue= getNormalMin(um,xvec[argument],tolx);
+ newFX = um.fminx;
+
+ } else {
+ debug("Number of hints:"+numberOfHints);
+ //System.out.println("Store:"+pal.misc.Utils.toString(store_,numberOfHints));
+ double min = olf_.getLowerBound();
+ double x = xvec[argument];
+ newArgValue = xvec[argument];
+ newFX = Double.POSITIVE_INFINITY;
+ for(int i = 0 ; i < numberOfHints ; i++) {
+ x =getBoundedMin(um, xvec[argument], tolx, min,store_[i]);
+ if(um.fminx<newFX) {
+ newArgValue=x; newFX = um.fminx;
+ }
+ min = store_[i];
+ }
+ double max = olf_.getUpperBound();
+ if(min!=max) {
+ x =getBoundedMin(um, xvec[argument], tolx, min,max);
+ if(um.fminx<newFX) {
+ newArgValue=x; newFX = um.fminx;
+ }
+ }
+ }
+ //If we actually found a better minimum...
+ if(newFX<=fx) {
+ if(debug_&&numberOfHints>0) {
+ //Do it old school!
+ getNormalMin(um,xvec[argument],tolx);
+ }
+ xvec[argument] = newArgValue;
+ olf_.setArgument(newArgValue);
+ if(monitor!=null) {
+ monitor.newMinimum(newFX,xvec,base_);
+ }
+ fx = newFX;
+
+
+ }
+ if(debug_) {
+ System.out.println(argument+":"+newFX+" "+fx+" "+um.fminx+" "+(um.fminx-newFX)+" "+((um.fminx<newFX) ? "Bad" : "Good!"));
+ }
+ numFun += um.numFun;
+ }
+ return fx;
+ }
+ }
+
+}
diff --git a/src/jebl/math/Random.java b/src/jebl/math/Random.java
new file mode 100644
index 0000000..80377d4
--- /dev/null
+++ b/src/jebl/math/Random.java
@@ -0,0 +1,193 @@
+/*
+ * Random.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.math;
+
+/**
+ * Random number generation.
+ *
+ * @author Matthew Goode
+ * @author Alexei Drummond
+ *
+ * @version $Id: Random.java 370 2006-06-29 18:57:56Z rambaut $
+ */
+public class Random {
+
+ private Random() {}
+
+ /**
+ * A random number generator that is initialized with the clock when this
+ * class is loaded into the JVM. Use this for all random numbers.
+ * Note: This method or getting random numbers in not thread-safe. Since
+ * MersenneTwisterFast is currently (as of 9/01) not synchronized using
+ * this function may cause concurrency issues. Use the static get methods of the
+ * MersenneTwisterFast class for access to a single instance of the class, that
+ * has synchronization.
+ */
+ private static final MersenneTwisterFast random = new MersenneTwisterFast();
+
+ // Chooses one category if a cumulative probability distribution is given
+ public static int randomChoice(double[] cf)
+ {
+
+ double U = random.nextDouble();
+
+ int s;
+ if (U <= cf[0])
+ {
+ s = 0;
+ }
+ else
+ {
+ for (s = 1; s < cf.length; s++)
+ {
+ if (U <= cf[s] && U > cf[s-1])
+ {
+ break;
+ }
+ }
+ }
+
+ return s;
+ }
+
+ /**
+ * @return a new double array where all the values sum to 1.
+ * Relative ratios are preserved.
+ */
+ public static double[] getNormalized(double[] array) {
+ double[] newArray = new double[array.length];
+ double total = getTotal(array);
+ for(int i = 0 ; i < array.length ; i++) {
+ newArray[i] = array[i]/total;
+ }
+ return newArray;
+ }
+
+ /**
+ * @param end the index of the element after the last one to be included
+ * @return the total of a the values in a range of an array
+ */
+ public static double getTotal(double[] array, int start, int end) {
+ double total = 0.0;
+ for(int i = start ; i < end; i++) {
+ total+=array[i];
+ }
+ return total;
+ }
+
+ /**
+ * @return the total of the values in an array
+ */
+ public static double getTotal(double[] array) {
+ return getTotal(array,0, array.length);
+
+ }
+
+ // ===================== Static access methods to the private random instance ===========
+
+ /** Access a default instance of this class, access is synchronized */
+ public static void setSeed(long seed) {
+ synchronized(random) {
+ random.setSeed(seed);
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static byte nextByte() {
+ synchronized(random) {
+ return random.nextByte();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static boolean nextBoolean() {
+ synchronized(random) {
+ return random.nextBoolean();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static void nextBytes(byte[] bs) {
+ synchronized(random) {
+ random.nextBytes(bs);
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static char nextChar() {
+ synchronized(random) {
+ return random.nextChar();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static double nextGaussian() {
+ synchronized(random) {
+ return random.nextGaussian();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static double nextDouble() {
+ synchronized(random) {
+ return random.nextDouble();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static float nextFloat() {
+ synchronized(random) {
+ return random.nextFloat();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static long nextLong() {
+ synchronized(random) {
+ return random.nextLong();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static short nextShort() {
+ synchronized(random) {
+ return random.nextShort();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static int nextInt() {
+ synchronized(random) {
+ return random.nextInt();
+ }
+ }
+ /** Access a default instance of this class, access is synchronized */
+ public static int nextInt(int n) {
+ synchronized(random) {
+ return random.nextInt(n);
+ }
+ }
+
+ /**
+ * Shuffles an array.
+ */
+ public static void shuffle(int[] array) {
+ synchronized(random) {
+ random.shuffle(array);
+ }
+ }
+ /**
+ * Shuffles an array. Shuffles numberOfShuffles times
+ */
+ public static void shuffle(int[] array, int numberOfShuffles) {
+ synchronized(random) {
+ random.shuffle(array, numberOfShuffles);
+ }
+ }
+ /**
+ * Returns an array of shuffled indices of length l.
+ * @param l length of the array required.
+ */
+ public static int[] shuffled(int l) {
+ synchronized(random) {
+ return random.shuffled(l);
+ }
+ }
+}
diff --git a/src/jebl/math/UnivariateFunction.java b/src/jebl/math/UnivariateFunction.java
new file mode 100644
index 0000000..41fb5ba
--- /dev/null
+++ b/src/jebl/math/UnivariateFunction.java
@@ -0,0 +1,45 @@
+// UnivariateFunction.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * interface for a function of one variable
+ *
+ * @author Korbinian Strimmer
+ */
+public interface UnivariateFunction
+{
+ /**
+ * compute function value
+ *
+ * @param argument function argument
+ *
+ * @return function value
+ */
+ double evaluate(double argument);
+
+ /**
+ * get lower bound of argument
+ *
+ * @return lower bound
+ */
+ double getLowerBound();
+
+ /**
+ * get upper bound of argument
+ *
+ * @return upper bound
+ */
+ double getUpperBound();
+}
diff --git a/src/jebl/math/UnivariateMinimum.java b/src/jebl/math/UnivariateMinimum.java
new file mode 100644
index 0000000..53c733a
--- /dev/null
+++ b/src/jebl/math/UnivariateMinimum.java
@@ -0,0 +1,509 @@
+// UnivariateMinimum.java
+//
+// (c) 2006- JEBL development team
+//
+// based on LGPL code from the Phylogenetic Analysis Library (PAL),
+// http://www.cebl.auckland.ac.nz/pal-project/
+// which is (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+
+package jebl.math;
+
+
+/**
+ * minimization of a real-valued function of one variable
+ * without using derivatives.
+ *
+ * <p>algorithm: Brent's golden section method
+ * (Richard P. Brent. 1973. Algorithms for finding zeros and extrema
+ * of functions without calculating derivatives. Prentice-Hall.)
+ *
+ * @version $Id: UnivariateMinimum.java 526 2006-11-13 20:45:42Z nut $
+ *
+ * @author Korbinian Strimmer
+ */
+public class UnivariateMinimum
+{
+ //
+ // Public stuff
+ //
+
+ /** last minimum */
+ public double minx;
+
+ /** function value at minimum */
+ public double fminx;
+
+ /** curvature at minimum */
+ public double f2minx;
+
+ /** total number of function evaluations neccessary */
+ public int numFun;
+
+ /**
+ * maximum number of function evaluations
+ * (default 0 indicates no limit on calls)
+ */
+ public int maxFun = 0;
+
+ /**
+ * Find minimum
+ * (first estimate given)
+ *
+ * @param x first estimate
+ * @param f function
+ *
+ * @return position of minimum
+ */
+ public double findMinimum(double x, UnivariateFunction f)
+ {
+ double tol = MachineAccuracy.EPSILON;
+
+ return optimize(x, f, tol);
+ }
+
+ /**
+ * Find minimum
+ * (first estimate given, desired number of fractional digits specified)
+ *
+ * @param x first estimate
+ * @param f function
+ * @param fracDigits desired fractional digits
+ *
+ * @return position of minimum
+ */
+ public double findMinimum(double x, UnivariateFunction f, int fracDigits)
+ {
+ double tol = Math.pow(10, -1-fracDigits);
+
+ double optx = optimize(x, f, tol);
+
+ //return trim(optx, fracDigits);
+ return optx;
+ }
+
+ /**
+ * Find minimum
+ * (no first estimate given)
+ *
+ * @param f function
+ *
+ * @return position of minimum
+ */
+ public double findMinimum(UnivariateFunction f)
+ {
+ double tol = MachineAccuracy.EPSILON;
+
+ return optimize(f, tol);
+ }
+
+ /**
+ * Find minimum
+ * (no first estimate given, desired number of fractional digits specified)
+ *
+ * @param f function
+ * @param fracDigits desired fractional digits
+ *
+ * @return position of minimum
+ */
+ public double findMinimum(UnivariateFunction f, int fracDigits)
+ {
+ double tol = Math.pow(10, -1-fracDigits);
+
+ double optx = optimize(f, tol);
+
+ //return trim(optx, fracDigits);
+ return optx;
+ }
+
+ /**
+ * The actual optimization routine (Brent's golden section method)
+ *
+ * @param f univariate function
+ * @param tol absolute tolerance of each parameter
+ * @param lowerBound the lower bound of input
+ * @param upperBound the upper bound of input
+ *
+ * @return position of minimum
+ */
+ public double optimize(UnivariateFunction f, double tol, double lowerBound, double upperBound)
+ {
+ numFun = 2;
+ return minin(lowerBound, upperBound, f.evaluate(lowerBound), f.evaluate(upperBound), f, tol);
+ }
+ /**
+ * The actual optimization routine (Brent's golden section method)
+ *
+ * @param f univariate function
+ * @param tol absolute tolerance of each parameter
+ *
+ * @return position of minimum
+ */
+ public double optimize(UnivariateFunction f, double tol)
+ {
+ return optimize(f,tol,f.getLowerBound(), f.getUpperBound());
+ }
+
+ /**
+ * The actual optimization routine (Brent's golden section method)
+ *
+ * @param x initial guess
+ * @param f univariate function
+ * @param tol absolute tolerance of each parameter
+ * @param lowerBound the lower bound of input
+ * @param upperBound the upper bound of input
+ *
+ * @return position of minimum
+ */
+ public double optimize(double x, UnivariateFunction f, double tol, double lowerBound, double upperBound)
+ {
+ double[] range = bracketize(lowerBound, x, upperBound, f);
+
+ return minin(range[0], range[1], range[2], range[3], f, tol);
+ }
+ /**
+ * The actual optimization routine (Brent's golden section method)
+ *
+ * @param x initial guess
+ * @param f univariate function
+ * @param tol absolute tolerance of each parameter
+ * note bounded by the given bounds of the function f
+ *
+ * @return position of minimum
+ */
+ public double optimize(double x, UnivariateFunction f, double tol)
+ {
+
+ return optimize(x,f,tol,f.getLowerBound(),f.getUpperBound());
+ }
+ //
+ // Private stuff
+ //
+
+ private static final double C = (3.0- Math.sqrt(5.0))/2.0; // = 0.38197
+ private static final double GOLD = (Math.sqrt(5.0) + 1.0)/2.0; // = 1.61803
+ private static final double delta = 0.01; // Determines second trial point
+
+ // trim x to have a specified number of fractional digits
+ private double trim(double x, int fracDigits)
+ {
+ double m = Math.pow(10, fracDigits);
+
+ return Math.round(x*m)/m;
+ }
+
+ private double constrain(double x, boolean toMax, double min, double max)
+ {
+ if (toMax)
+ {
+ if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+ else
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else
+ {
+ return x;
+ }
+ }
+ }
+
+ private double[] bracketize(double min, double a, double max, UnivariateFunction f)
+ {
+ if (min > max)
+ {
+ throw new IllegalArgumentException("Argument min (" + min +
+ ") larger than argument max (" + max + ")");
+ }
+
+ if (a < min)
+ {
+ a = min;
+ }
+ else if (a > max)
+ {
+ a = max;
+ }
+
+
+ if (a < min || a > max)
+ {
+ throw new IllegalArgumentException("Starting point not in given range ("
+ + min + ", " + a + ", " + max + ")");
+ }
+
+
+ // Get second point
+ double b;
+ if (a - min < max - a)
+ {
+ b = a + delta*(max - a);
+ }
+ else
+ {
+ b = a - delta*(a - min);
+ }
+
+ numFun = 0;
+
+ double fa = f.evaluate(a); numFun++;
+ double fb = f.evaluate(b); numFun++;
+
+ double tmp;
+ if (fb > fa)
+ {
+ tmp = a; a = b; b = tmp;
+ tmp = fa; fa = fb; fb = tmp;
+ }
+
+ // From here on we always have fa >= fb
+ // Our aims is to determine a new point c with fc >= fb
+
+ // Direction of search (towards min or towards max)
+ boolean searchToMax;
+ double ulim;
+ if (b > a)
+ {
+ searchToMax = true;
+ ulim = max;
+ }
+ else
+ {
+ searchToMax = false;
+ ulim = min;
+ }
+
+ // First guess: default magnification
+ double c = b + GOLD * (b - a);
+ c = constrain(c, searchToMax, min, max);
+ double fc = f.evaluate(c); numFun++;
+
+ while (fb > fc)
+ {
+ // Compute u as minimum of a parabola through a, b, c
+ double r = (b - a) * (fb - fc);
+ double q = (b - c) * (fb - fa);
+ if (q == r)
+ {
+ q += MachineAccuracy.EPSILON;
+ }
+ double u = b - ((b - c) * q - (b - a) * r) / 2.0 / (q - r);
+ u = constrain(u, searchToMax, min, max);
+ double fu = 0; // Don't evaluate now
+
+ boolean magnify = false;
+
+ // Check out all possibilities
+
+ // u is between b and c
+ if ((b - u) * (u - c) > 0)
+ {
+ fu = f.evaluate(u); numFun++;
+
+ // minimum between b and c
+ if (fu < fc)
+ {
+ a = b; b = u;
+ fa = fb; fb = fu;
+
+ break;
+ }
+ // minimum between a and u
+ else if (fu > fb)
+ {
+ c = u;
+ fc = fu;
+
+ break;
+ }
+
+ magnify = true;
+ }
+ // u is between c and limit
+ else if ((c - u) * (u - ulim) > 0)
+ {
+ fu = f.evaluate(u); numFun++;
+
+ // u is not a minimum
+ if (fu < fc)
+ {
+ b = c; c = u;
+ fb = fc; fc = fu;
+
+ magnify = true;
+ }
+ }
+ // u equals limit
+ else if (u == ulim)
+ {
+ fu = f.evaluate(u); numFun++;
+ }
+ // All other cases
+ else
+ {
+ magnify = true;
+ }
+
+ if (magnify)
+ {
+ // Next guess: default magnification
+ u = c + GOLD * (c - b);
+ u = constrain(u, searchToMax, min, max);
+ fu = f.evaluate(u); numFun++;
+ }
+
+ a = b; b = c; c = u;
+ fa = fb; fb = fc; fc = fu;
+ }
+
+ // Once we are here be have a minimum in [a, c]
+ double[] result = new double[4];
+ result[0] = a;
+ result[1] = c;
+ result[2] = fa;
+ result[3] = fc;
+ return result;
+ }
+
+
+ private double minin(double a, double b, double fa , double fb, UnivariateFunction f, double tol)
+ {
+ double z, d = 0, e, m, p, q, r, t, u, v, w, fu, fv, fw, fz, tmp;
+
+ if (tol <= 0)
+ {
+ throw new IllegalArgumentException("Nonpositive absolute tolerance tol");
+ }
+
+ if (a == b)
+ {
+ minx = a;
+ fminx = fa;
+
+ f2minx = NumericalDerivative.secondDerivative(f, minx);
+
+ return minx;
+
+ //throw new IllegalArgumentException("Borders of range not distinct");
+ }
+
+ if (b < a)
+ {
+ tmp = a; a = b; b = tmp;
+ tmp = fa; fa = fb; fb = tmp;
+ }
+
+ w = a; fw = fa;
+ z = b; fz = fb;
+ if (fz > fw) // Exchange z and w
+ {
+ v = z; z = w; w = v;
+ v = fz; fz = fw; fw = v;
+ }
+ v = w;
+ fv = fw;
+ e = 0.0;
+ while (maxFun == 0 || numFun <= maxFun)
+ {
+ m = (a + b)*0.5;
+ double tol_act = MachineAccuracy.SQRT_EPSILON + tol; // Absolute tolerance
+ //double tol_act = MachineAccuracy.SQRT_EPSILON*Math.abs(z) + tol/3; // Actual tolerance
+ double tol_act2 = 2.0*tol_act;
+ if (Math.abs(z-m) <= tol_act2-(b - a)*0.5)
+ {
+ break;
+ }
+ p = q = r = 0.0;
+ if (Math.abs(e) > tol_act)
+ {
+ r = (z-w)*(fz-fv);
+ q = (z-v)*(fz-fw);
+ p = (z-v)*q-(z-w)*r;
+ q = (q-r)*2.0;
+ if (q > 0.0)
+ {
+ p = -p;
+ }
+ else
+ {
+ q = -q;
+ }
+ r = e;
+ e = d;
+ }
+ if (Math.abs(p) < Math.abs(q*r*0.5) && p > (a-z)*q && p < (b-z)*q)
+ {
+ d = p/q;
+ u = z+d;
+ if (u-(a) < tol_act2 || (b)-u < tol_act2)
+ {
+ d = ((z < m) ? tol_act : -tol_act);
+ }
+ }
+ else
+ {
+ e = ((z < m) ? b : a) - z;
+ d = C*e;
+ }
+ u = z + ((Math.abs(d) >= tol_act) ? d : ((d > 0.0) ? tol_act : -tol_act));
+ fu = f.evaluate(u); numFun++;
+ if (fu <= fz)
+ {
+ if (u < z)
+ {
+ b = z;
+ }
+ else
+ {
+ a = z;
+ }
+ v = w;
+ fv = fw;
+ w = z;
+ fw = fz;
+ z = u;
+ fz = fu;
+ }
+ else
+ {
+ if (u < z)
+ {
+ a = u;
+ }
+ else
+ {
+ b = u;
+ }
+ if (fu <= fw)
+ {
+ v = w; fv = fw;
+ w = u; fw = fu;
+ }
+ else if (fu <= fv || v == w)
+ {
+ v = u;
+ fv = fu;
+ }
+ }
+ }
+ minx = z;
+ fminx = fz;
+
+ f2minx = NumericalDerivative.secondDerivative(f, minx);
+
+ return z;
+ }
+}
diff --git a/src/jebl/util/Attributable.java b/src/jebl/util/Attributable.java
new file mode 100644
index 0000000..50076dd
--- /dev/null
+++ b/src/jebl/util/Attributable.java
@@ -0,0 +1,68 @@
+/*
+ * Attributable.java
+ *
+ * (c) 2005 JEBL Development Team
+ *
+ * This package is distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.util;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Interface for associating attributeNames with an object.
+ *
+ * @version $Id: Attributable.java 575 2006-12-14 15:49:14Z twobeers $
+ *
+ * @author Andrew Rambaut
+ */
+public interface Attributable {
+
+ /**
+ * Sets an named attribute for this object.
+ * @param name the name of the attribute.
+ * @param value the new value of the attribute.
+ */
+ void setAttribute(String name, Object value);
+
+ /**
+ * @return an object representing the named attributed for this object.
+ * @param name the name of the attribute of interest, or null if the attribute doesn't exist.
+ */
+ Object getAttribute(String name);
+
+ /**
+ * @param name name of attribute to remove
+ */
+ void removeAttribute(String name);
+
+ /**
+ * @return an array of the attributeNames that this object has.
+ */
+ Set<String> getAttributeNames();
+
+ /**
+ * Gets the entire attribute map.
+ * @return an unmodifiable map
+ */
+ Map<String, Object> getAttributeMap();
+
+ public static class Utils {
+ Set<String> getAttributeNames(Collection<Attributable> attributables) {
+ Set<String> names = new HashSet<String>();
+
+ for (Attributable attributable : attributables) {
+ names.addAll(attributable.getAttributeNames());
+ }
+
+ return names;
+ }
+ }
+}
+
+
diff --git a/src/jebl/util/AttributableHelper.java b/src/jebl/util/AttributableHelper.java
new file mode 100644
index 0000000..6bb1d03
--- /dev/null
+++ b/src/jebl/util/AttributableHelper.java
@@ -0,0 +1,37 @@
+package jebl.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author rambaut
+ * Date: Nov 27, 2005
+ * Time: 1:31:50 PM
+ */
+public class AttributableHelper implements Attributable {
+
+ public void setAttribute(String name, Object value) {
+ attributeMap.put(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ return attributeMap.get(name);
+ }
+
+ public void removeAttribute(String name) {
+ attributeMap.remove(name);
+ }
+
+ public Set<String> getAttributeNames() {
+ return attributeMap.keySet();
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ return Collections.unmodifiableMap(attributeMap);
+ }
+
+ Map<String, Object> attributeMap = new HashMap<String, Object>();
+}
+
diff --git a/src/jebl/util/CompositeProgressListener.java b/src/jebl/util/CompositeProgressListener.java
new file mode 100644
index 0000000..e636069
--- /dev/null
+++ b/src/jebl/util/CompositeProgressListener.java
@@ -0,0 +1,197 @@
+package jebl.util;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A {@link jebl.util.ProgressListener} that is suitable for a task that consists of several subtasks.
+ * You specify the relative duration of each subtask, and then the subtasks' setProgress()
+ * calls with values between 0 and 1 are translated to reflect the overall progress on the whole
+ * (combined) task. In other words, each subtask reports progress as if it were the whole task,
+ * and the CompositeProgressListener translates this into overall progress.
+ * <p/>
+ * As the combined progress listener cannot know which subtask it is currently being called from,
+ * you have to explicitely let it know when a new subtask (not the first) starts, by calling
+ * {@link #beginNextSubtask()}. Thus when the constructor is passed an array of N doubles as its second
+ * argument, {@link #beginNextSubtask()} should be called precisely N-1 times.
+ * <p/>
+ * Alternatively, instead of calling {@link #beginNextSubtask()}after each subtask (except the last),
+ * you can instead call {@link #beginSubtask()} before each subtask (including the first)
+ * <p/>
+ *
+ * @author Tobias Thierer
+ * @version $Id: CompositeProgressListener.java 735 2007-07-04 01:45:12Z matt_kearse $
+ */
+public final class CompositeProgressListener extends ProgressListener {
+ protected int numOperations;
+ protected ProgressListener listener;
+ protected int currentOperationNum = 0;
+ protected double[] time;
+ protected double baseTime = 0.0; // overall progress (0..1) at the start of the current sub-operation
+ protected double currentOperationProgress = 0.0;
+ private boolean beganFirstSubTask=false;
+
+ /**
+ * construct a new composite ProgressListener.
+ *
+ * @param listener the ProgressListener that all progress reports are forwarded to after adjusting them for the currently active sub-task
+ * @param operationDuration a list of relative weightings to give each sub task.
+ */
+ public CompositeProgressListener(ProgressListener listener, double ... operationDuration) {
+ numOperations = operationDuration.length;
+ if (numOperations == 0) {
+ throw new IllegalArgumentException("Composite operation must have > 0 subtasks");
+ }
+ if (listener == null) {
+ this.listener = ProgressListener.EMPTY;
+ } else {
+ this.listener = listener;
+ }
+ this.time = operationDuration.clone();
+
+ // scale times to a sum of 1
+ double totalTime = 0.0;
+ for (double d : operationDuration) {
+ if (d < 0.0) {
+ throw new IllegalArgumentException("Operation cannot take negative time: " + d);
+ }
+ totalTime += d;
+ }
+ for (int i = 0; i < numOperations; i++)
+ this.time[i] = (operationDuration[i] / totalTime);
+ }
+
+ public static CompositeProgressListener forFiles(ProgressListener listener, List<File> files) {
+ int n = files.size();
+ double[] lengths = new double[n];
+ int i =0;
+ for (File file : files) {
+ lengths[i++] = (double) file.length();
+ }
+ return new CompositeProgressListener(listener, lengths);
+ }
+
+ /**
+ * Used as an alternative to {@link #beginNextSubtask()}.
+ * Instead of calling {@link #beginNextSubtask()} once after each subtask
+ * (except the last), you can instead call beginSubTask at the beginning
+ * of every subtask including the first.
+ */
+ public void beginSubtask() {
+ if (!beganFirstSubTask) {
+ beganFirstSubTask = true;
+ } else {
+ beginNextSubtask();
+ }
+ }
+
+ /**
+ * Used as an alternative to {@link #beginNextSubtask()}.
+ * Instead of calling {@link #beginNextSubtask()} once after each subtask
+ * (except the last), you can instead call beginSubTask at the beginning
+ * of every subtask including the first.
+ * @param message a message to be displayed to the user as part of the progress
+ */
+ public void beginSubtask(String message) {
+ setMessage(message);
+ beginSubtask();
+ }
+
+ protected void _setProgress(double fractionCompleted) {
+ currentOperationProgress = fractionCompleted;
+ listener._setProgress(baseTime + fractionCompleted * time[currentOperationNum]);
+ }
+
+ protected void _setIndeterminateProgress() {
+ listener._setIndeterminateProgress();
+ }
+
+ protected void _setMessage(String message) {
+ listener._setMessage(message);
+ }
+
+ public boolean isCanceled() {
+ return listener.isCanceled();
+ }
+
+ public boolean addProgress(double fractionCompletedDiff) {
+ return setProgress(currentOperationProgress + fractionCompletedDiff);
+ }
+
+ public boolean setComplete() {
+ return setProgress(1.0);
+ }
+
+ /**
+ * @return true if there is another subtask available after the current one
+ */
+ public boolean hasNextSubtask() {
+ return (currentOperationNum < (numOperations - 1));
+ }
+
+ /**
+ * Clear all progress, including that of previous subtasks.
+ * Note: if the task has already been canceled, this does not reset its status to non-canceled.
+ */
+ public void clearAllProgress () {
+ currentOperationNum = 0;
+ baseTime = 0.0;
+ setProgress(0.0);
+ //listener.setProgress(0);
+ }
+
+ /**
+ * Convenience method to start the next operation AND set a new message.
+ * @param message message to set (will be passed to setMessage()
+ */
+ public void beginNextSubtask(String message) {
+ beginNextSubtask();
+ setMessage(message);
+ }
+
+ /**
+ * begins the next subtask. Should not be called on the first subtask, but should only be called
+ * to start tasks after the first one. If you wish to call a begin subtask method
+ * for each task including the first, use {@link #beginSubtask()} instead.
+ */
+ public void beginNextSubtask() {
+ setComplete();
+ if (!hasNextSubtask()) {
+ throw new IllegalStateException(currentOperationNum + " " + numOperations);
+ }
+ baseTime += time[currentOperationNum];
+ currentOperationNum++;
+ currentOperationProgress = 0.0;
+ }
+
+// public Iterator<ProgressListener> iterator() {
+// final AtomicBoolean isFirst = new AtomicBoolean(true);
+// return new Iterator<ProgressListener>() {
+// public boolean hasNext() {
+// return hasNextOperation();
+// }
+//
+// public ProgressListener next() {
+// if (!hasNext()) {
+// throw new NoSuchElementException();
+// }
+// if (isFirst.get()) {
+// isFirst.set(false);
+// } else {
+// startNextOperation();
+// }
+// return CompositeProgressListener.this;
+// }
+//
+// /**
+// * Currently not implemented, but may be implemented in the future.
+// *
+// * @throws UnsupportedOperationException
+// */
+// public void remove() {
+// throw new UnsupportedOperationException();
+// }
+// };
+// }
+
+}
diff --git a/src/jebl/util/FixedBitSet.java b/src/jebl/util/FixedBitSet.java
new file mode 100644
index 0000000..f37263f
--- /dev/null
+++ b/src/jebl/util/FixedBitSet.java
@@ -0,0 +1,227 @@
+package jebl.util;
+
+import java.util.Arrays;
+
+/**
+ * A bit-set of fixed size. Size is determined on creation.
+ *
+ * @author Joseph Heled
+ * @version $Id: FixedBitSet.java 591 2006-12-21 02:39:18Z pepster $
+ */
+public class FixedBitSet {
+ int[] bits;
+ int size;
+ //private int intSize = Integer.SIZE;
+
+ private final static int ADDRESS_BITS_PER_UNIT = 5;
+ private final static int BITS_PER_UNIT = 1 << ADDRESS_BITS_PER_UNIT;
+ private final static int BIT_INDEX_MASK = BITS_PER_UNIT - 1;
+
+
+ private static int unitIndex(int bitIndex) {
+ return bitIndex >> ADDRESS_BITS_PER_UNIT;
+ }
+
+ private int countBits(int b) {
+ int sum = 0;
+
+ while (b != 0) {
+ // remove most significant bit
+ b = b & (b - 1);
+ ++sum;
+ }
+ return sum;
+ }
+
+ /**
+ * Given a bit index, return a unit that masks that bit in its unit.
+ * @return the mask
+ */
+ private static int bit(int bitIndex) {
+ return 1 << (bitIndex & BIT_INDEX_MASK);
+ }
+
+ public FixedBitSet(int size) {
+ this.size = size;
+ bits = new int[(unitIndex(size - 1) + 1)];
+ }
+
+ public FixedBitSet(FixedBitSet bs) {
+ bits = bs.bits.clone();
+ size = bs.size;
+ }
+
+
+ public void set(int position) {
+ int unitIndex = unitIndex(position);
+ bits[unitIndex] |= bit(position);
+ }
+
+ public void clear(int position) {
+ int unitIndex = unitIndex(position);
+ bits[unitIndex] &= ~bit(position);
+ }
+
+ /**
+ * @param bitset
+ * @return true if bitset contains this set (this <= bitset)
+ */
+ public boolean setInclusion(final FixedBitSet bitset) {
+ for (int k = 0; k < bits.length; ++k) {
+ if (bits[k] != (bits[k] & bitset.bits[k])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void union(FixedBitSet b) {
+ for (int k = 0; k < Math.min(bits.length, b.bits.length); ++k) {
+ bits[k] |= b.bits[k];
+ }
+ }
+
+ public void intersect(FixedBitSet b) {
+ for (int k = 0; k < Math.min(bits.length, b.bits.length); ++k) {
+ bits[k] &= b.bits[k];
+ }
+ }
+
+ public void setMinus(FixedBitSet b) {
+ for (int k = 0; k < Math.min(bits.length, b.bits.length); ++k) {
+ bits[k] &= ~b.bits[k];
+ }
+ }
+
+ public int intersectCardinality(FixedBitSet b) {
+ int c = 0;
+ for (int k = 0; k < Math.min(bits.length, b.bits.length); ++k) {
+ c += countBits(bits[k] & b.bits[k]);
+ }
+ return c;
+ }
+
+ public static FixedBitSet complement(FixedBitSet b) {
+ FixedBitSet t = new FixedBitSet(b);
+ t.complement();
+ return t;
+ }
+
+ public void complement() {
+ int k;
+ for (k = 0; k < bits.length - 1; ++k) {
+ bits[k] = ~ bits[k];
+ }
+
+ bits[k] = ~bits[k];
+ // reset all higher order bits
+ final int mask = bit(size) - 1;
+ if( mask != 0 ) {
+ bits[k] &= mask;
+ }
+ }
+
+ private final static byte firstBitLocation[] = {
+ -1, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
+
+ private int firstOnBit(int i) {
+ for (int k = 0; k < 4; ++k) {
+ char b = (char) (i & 0xff);
+ if (b != 0) {
+ return 8 * k + firstBitLocation[b];
+ }
+ i = i >> 8;
+ }
+ return -1;
+ }
+
+ /**
+ * Iteration helper. A typical iteration on set bits might be
+ * FixedBitSet b;
+ * for(int i = b.nextOnBit(0); i >= 0; i = b.nextOnBit(i+1)) ...
+ *
+ * @param fromIndex
+ * @return Next set member whose index is >= fromIndex. -1 if none.
+ */
+ public int nextOnBit(int fromIndex) {
+ int u = unitIndex(fromIndex);
+ int testIndex = (fromIndex & BIT_INDEX_MASK);
+ int unit = bits[u] >> testIndex;
+
+ if (unit == 0) {
+ testIndex = 0;
+
+ while ((unit == 0) && (u < bits.length - 1))
+ unit = bits[++u];
+ }
+
+ if (unit == 0)
+ return -1;
+
+ testIndex += firstOnBit(unit);
+ return ((u * BITS_PER_UNIT) + testIndex);
+ }
+
+ public int cardinality() {
+ int sum = 0;
+ for (int b : bits) {
+ sum += countBits(b);
+ }
+ return sum;
+ }
+
+ public boolean contains(final int i) {
+ final int unitIndex = unitIndex(i);
+ return (bits[unitIndex] & bit(i)) != 0;
+ }
+
+ public int hashCode() {
+ int code = 0;
+
+ for (int bit : bits) {
+ code = code ^ bit;
+ }
+ return code;
+ }
+
+ public boolean equals(Object x) {
+ if (x instanceof FixedBitSet) {
+ final FixedBitSet b = (FixedBitSet) x;
+
+ return b.size == size && Arrays.equals(bits, b.bits);
+ }
+ return false;
+ }
+
+ public String toString() {
+ StringBuilder rep = new StringBuilder();
+ rep.append("{");
+ for (int b = 0; b < size; ++b) {
+ if (contains(b)) {
+ if (rep.length() > 0) {
+ rep.append("," + b);
+ } else {
+ rep.append("" + b);
+ }
+ }
+ }
+ rep.append("}");
+ return rep.toString();
+ }
+
+}
diff --git a/src/jebl/util/NumberFormatter.java b/src/jebl/util/NumberFormatter.java
new file mode 100644
index 0000000..460e2f4
--- /dev/null
+++ b/src/jebl/util/NumberFormatter.java
@@ -0,0 +1,136 @@
+/*
+ * NumberFormatter.java
+ *
+ * (c) 2002-2006 JEBL Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package jebl.util;
+
+import java.text.DecimalFormat;
+
+/**
+ * An interface for a numerical column in a log.
+ *
+ * @version $Id: NumberFormatter.java 280 2006-04-04 22:30:40Z pepster $
+ *
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ */
+
+public class NumberFormatter {
+
+ private int sf = -1;
+ private int dp = -1;
+
+ private double upperCutoff;
+ private double[] cutoffTable;
+ private DecimalFormat decimalFormat = new DecimalFormat();
+ private DecimalFormat scientificFormat = null;
+
+ public NumberFormatter(int sf) {
+ setSignificantFigures(sf);
+ }
+
+ /**
+ * Set the number of significant figures to display when formatted.
+ * Setting this overrides the decimal places option.
+ */
+ public void setSignificantFigures(int sf) {
+ this.sf = sf;
+ this.dp = -1;
+
+ upperCutoff = Math.pow(10,sf-1);
+ cutoffTable = new double[sf];
+ long num = 10;
+ for (int i = 0; i < cutoffTable.length; i++) {
+ cutoffTable[i] = (double)num;
+ num *= 10;
+ }
+ decimalFormat.setGroupingUsed(false);
+ decimalFormat.setMinimumIntegerDigits(1);
+ decimalFormat.setMaximumFractionDigits(sf-1);
+ decimalFormat.setMinimumFractionDigits(sf-1);
+ scientificFormat = new DecimalFormat(getPattern(sf));
+ }
+
+ /**
+ * Get the number of significant figures to display when formatted.
+ * Returns -1 if maximum s.f. are to be used.
+ */
+ public int getSignificantFigures() { return sf; }
+
+ /**
+ * Set the number of decimal places to display when formatted.
+ * Setting this overrides the significant figures option.
+ */
+ public void setDecimalPlaces(int dp) {
+ this.dp = dp;
+ this.sf = -1;
+ }
+
+ /**
+ * Get the number of decimal places to display when formatted.
+ * Returns -1 if maximum d.p. are to be used.
+ */
+ public int getDecimalPlaces() { return dp; }
+
+
+ /**
+ * Returns a string containing the current value for this column with
+ * appropriate formatting.
+ *
+ * @return the formatted string.
+ */
+ public String getFormattedValue(double value) {
+
+ if (dp < 0 && sf < 0) {
+ // return it at full precision
+ return Double.toString(value);
+ }
+
+ int numFractionDigits = 0;
+
+ if (dp < 0) {
+
+ double absValue = Math.abs(value);
+
+ if ((absValue > upperCutoff) || (absValue < 0.1)) {
+
+ return scientificFormat.format(value);
+
+ } else {
+
+ numFractionDigits = getNumFractionDigits(value);
+ }
+
+ } else {
+
+ numFractionDigits = dp;
+ }
+
+ decimalFormat.setMaximumFractionDigits(numFractionDigits);
+ decimalFormat.setMinimumFractionDigits(numFractionDigits);
+ return decimalFormat.format(value);
+ }
+
+ private int getNumFractionDigits(double value) {
+ value = Math.abs(value);
+ for (int i = 0; i < cutoffTable.length; i++) {
+ if (value < cutoffTable[i]) return sf-i-1;
+ }
+ return sf - 1;
+ }
+
+ private String getPattern(int sf) {
+ String pattern = "0.";
+ for (int i =0; i < sf-1; i++) {
+ pattern += "#";
+ }
+ pattern += "E0";
+ return pattern;
+ }
+
+}
diff --git a/src/jebl/util/ProgressListener.java b/src/jebl/util/ProgressListener.java
new file mode 100644
index 0000000..a722820
--- /dev/null
+++ b/src/jebl/util/ProgressListener.java
@@ -0,0 +1,165 @@
+package jebl.util;
+
+import org.virion.jam.util.SimpleListener;
+
+import java.awt.*;
+
+/**
+ * @author Matt Kearse
+ *
+ * @version $Id: ProgressListener.java 721 2007-06-05 20:42:51Z matt_kearse $
+ *
+ * ProgressListener guarantees the following contract:
+ *
+ * A call to any of the methods setProgress(), setMessage(), isCanceled() and
+ * setIndeterminateProgress() at a given time yields the same result as a call
+ * to another of these methods would have resulted at the same time.
+ *
+ * Once the task whose progress we are observing has been canceled, calls
+ * to either of these methods reflect this. This does not prevent subclasses
+ * from introducing a way to "reset" a ProgressListener that was previously
+ * canceled from not being canceled any more.
+ *
+ * Any object may exhibit undefined behaviour when dealing with a ProgressListener
+ * that is not fulfilling this contract.
+ */
+public abstract class ProgressListener {
+ /**
+ * @param fractionCompleted a number between 0 and 1 inclusive
+ * representing the fraction of the operation completed.
+ * If you are unsure of the fraction completed, call {@link #setIndeterminateProgress} instead.
+ *
+ * @return true if the user has requested that this operation be canceled.
+ */
+ public final boolean setProgress(double fractionCompleted) {
+ _setProgress(fractionCompleted);
+ return isCanceled();
+ }
+
+ /**
+ * This method is a hook called from {@link #setProgress} to allow subclasses a
+ * custom reaction to setProgress events. Currently, subclasses are required to
+ * implement this method, but in the future it may get an empty default
+ * implementation to make it optional for subclasses to subscribe to setProgress
+ * events.
+ */
+ protected abstract void _setProgress(double fractionCompleted);
+
+ /**
+ * Sets indefinite progress (i.e. "some progress has happened, but I don't
+ * know how close we are to finishing").
+ * @return true if the user has requested that this operation be canceled.
+ */
+ public final boolean setIndeterminateProgress() {
+ _setIndeterminateProgress();
+ return isCanceled();
+ }
+
+ /**
+ * This method is a hook called from {@link #setIndeterminateProgress} to
+ * allow subclasses a custom reaction to setIndeterminateProgress events.
+ * Currently, subclasses are required to implement this method, but in the
+ * future it may get an empty default implementation to make it optional
+ * for subclasses to subscribe to setIndeterminateProgress events.
+ */
+ protected abstract void _setIndeterminateProgress();
+
+ /**
+ * Set visible user message.
+ * @param message
+ * @return true if the user has requested that this operation be canceled.
+ */
+ public final boolean setMessage(String message) {
+ _setMessage(message);
+ return isCanceled();
+ }
+
+ /**
+ * Set an image associated with the current progress. A progress listener
+ * may choose to optionally display this image wherever is appropriate.
+ * @param image an image
+ * @return true if the user has requested that this operation be canceled.
+ */
+ public final boolean setImage(Image image) {
+ _setImage(image);
+ return isCanceled();
+ }
+
+ /**
+ *
+ * This method is a hook called from {@link #setImage} to allow subclasses a
+ * custom reaction to setImage events
+ *
+ * @param image the image
+ */
+ protected void _setImage(Image image) {
+
+ }
+
+ /**
+ * Adds an action that can choose to provide feedback. For example,
+ * an operation may choose to provide a "Skip to next step" button
+ * alongside the cancel button. There is no requirement that a
+ * ProgressListener actually present this to the user - it may choose
+ * to ignore this method, in which case <code> listener </code> will
+ * never be fired.
+ * @param label a label describing this feedback action. For example, "Skip to next step"
+ * @param listener a listener to be notified when the user chooses to invoke
+ * this action
+ */
+ public void addFeedbackAction(String label, SimpleListener listener) {
+
+ }
+
+ /**
+ * Removes a feedback action previously added using
+ * {@link #addFeedbackAction(String, org.virion.jam.util.SimpleListener)}.
+ * @param label The label used as a parameter to {@link #addFeedbackAction(String, org.virion.jam.util.SimpleListener)}
+ */
+ public void removeFeedbackAction(String label) {
+
+ }
+
+
+
+ /**
+ * This method is a hook called from {@link #setMessage} to allow subclasses a
+ * custom reaction to setMessage events. Currently, subclasses are required to
+ * implement this method, but in the future it may get an empty default
+ * implementation to make it optional for subclasses to subscribe to setMessage
+ * events.
+ */
+ protected abstract void _setMessage(String message);
+
+ /**
+ * This method must be implemented by all subclasses. It is called from
+ * {@link #setProgress}, {@link #setIndeterminateProgress} and {@link #setMessage}
+ * to determine the return value of these methods.
+ *
+ * @return true if the user has requested that this operation be canceled.
+ */
+ public abstract boolean isCanceled();
+
+
+ /**
+ * A ProgressListener that ignores all events and always returns false from
+ * {@link #isCanceled}. Useful when you don't care about the progress
+ * results or canceling the operation.
+ */
+ public static final ProgressListener EMPTY = new EmptyProgressListener();
+
+ private static class EmptyProgressListener extends ProgressListener {
+ protected void _setProgress(double fractionCompleted) {
+ }
+
+ protected void _setMessage(String message) {
+ }
+
+ public boolean isCanceled() {
+ return false;
+ }
+
+ protected void _setIndeterminateProgress() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jebl/util/Utils.java b/src/jebl/util/Utils.java
new file mode 100644
index 0000000..dfdcde3
--- /dev/null
+++ b/src/jebl/util/Utils.java
@@ -0,0 +1,455 @@
+// Utils.java
+//
+// (c) 1999-2001 PAL Development Core Team
+//
+// This package may be distributed under the
+// terms of the Lesser GNU General Public License (LGPL)
+
+package jebl.util;
+
+/**
+ * Provides some miscellaneous methods.
+ *
+ * @author Matthew Goode
+ * @version $Id: Utils.java 264 2006-03-20 17:59:22Z pepster $
+ */
+public class Utils {
+ /**
+ * Test if a string occurs within a set
+ *
+ * @param set the set of strings
+ * @param query the query string
+ * @return true if the query string is in the set (as determined by object equality)
+ */
+ public static boolean isContains(String[] set, String query) {
+ for (String string : set) {
+ if (query.equals(string)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clones an array of doubles
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static double[] getCopy(double[] array) {
+ if (array == null) {
+ return null;
+ }
+ double[] copy = new double[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+
+ /**
+ * Calculate the total of an array
+ *
+ * @param array The array to sum up
+ * @return the sum of all the elements
+ */
+ public static double getSum(double[] array) {
+ double total = 0;
+ for (double a : array) {
+ total += a;
+ }
+ return total;
+ }
+
+ /**
+ * Calculate the max of an array
+ *
+ * @param array The array to check
+ * @return the max of all the elements
+ */
+ public static double getMax(double[] array) {
+ return getMax(array, 0, array.length);
+ }
+
+ /**
+ * Calculate the max of an array
+ *
+ * @param array The array to check
+ * @param start the first index to check
+ * @param end the index after the last index to check
+ * @return the max of all the elements
+ */
+ public static double getMax(double[] array, int start, int end) {
+ double max = Double.NEGATIVE_INFINITY;
+ for (int i = start; i < end; i++) {
+ final double v = array[i + start];
+ if (v > max) {
+ max = v;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Calculate the min of an array
+ *
+ * @param array The array to check
+ * @return the min of all the elements
+ */
+ public static double getMin(double[] array) {
+ double min = Double.POSITIVE_INFINITY;
+ for (double v : array) {
+ if (v < min) {
+ min = v;
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Calculate the mean value of an array
+ *
+ * @param array the values
+ * @return the average
+ */
+ public static double getMean(double[] array) {
+ return getSum(array) / array.length;
+ }
+
+ /**
+ * Clones an array of doubles from index start (inclusive) to index end (exclusive)
+ *
+ * @return null if input is null
+ */
+ public static double[] getCopy(double[] array, int start, int end) {
+ if (array == null) {
+ return null;
+ }
+ double[] copy = new double[end - start];
+ System.arraycopy(array, start, copy, 0, copy.length);
+ return copy;
+ }
+
+ /**
+ * Clones an array of doubles from index start (inclusive) to end
+ *
+ * @return null if input is null
+ */
+ public static double[] getCopy(double[] array, int start) {
+ return getCopy(array, start, array.length);
+ }
+
+ /**
+ * Clones an array of bytes
+ *
+ * @param array the bytes to copy
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static byte[] getCopy(byte[] array) {
+ if (array == null) {
+ return null;
+ }
+ byte[] copy = new byte[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+
+ /**
+ * Clones an array of Strings
+ *
+ * @param array the strings to copy
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static String[] getCopy(String[] array) {
+ if (array == null) {
+ return null;
+ }
+ String[] copy = new String[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+
+ /**
+ * Clones an array of doubles
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static double[][] getCopy(double[][] array) {
+ if (array == null) {
+ return null;
+ }
+ double[][] copy = new double[array.length][];
+ for (int i = 0; i < copy.length; i++) {
+ copy[i] = new double[array[i].length];
+ System.arraycopy(array[i], 0, copy[i], 0, array[i].length);
+ }
+ return copy;
+ }
+
+ /**
+ * Clones a matrix of ints
+ *
+ * @param matrix the matrix to clone
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static int[][] getCopy(int[][] matrix) {
+ if (matrix == null) {
+ return null;
+ }
+ int[][] copy = new int[matrix.length][];
+ for (int i = 0; i < copy.length; i++) {
+ copy[i] = new int[matrix[i].length];
+ System.arraycopy(matrix[i], 0, copy[i], 0, matrix[i].length);
+ }
+ return copy;
+ }
+
+ /**
+ * Clones an array of doubles
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static double[][][] getCopy(double[][][] array) {
+ if (array == null) {
+ return null;
+ }
+ double[][][] copy = new double[array.length][][];
+ for (int i = 0; i < copy.length; i++) {
+ copy[i] = getCopy(array[i]);
+ }
+ return copy;
+ }
+
+ /**
+ * Clones an array of bytes
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static byte[][] getCopy(byte[][] array) {
+ if (array == null) {
+ return null;
+ }
+ byte[][] copy = new byte[array.length][];
+ for (int i = 0; i < copy.length; i++) {
+ copy[i] = new byte[array[i].length];
+ System.arraycopy(array[i], 0, copy[i], 0, array[i].length);
+ }
+ return copy;
+ }
+
+ /**
+ * Clones an array of booleans
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static boolean[][] getCopy(boolean[][] array) {
+ if (array == null) {
+ return null;
+ }
+ boolean[][] copy = new boolean[array.length][];
+ for (int i = 0; i < copy.length; i++) {
+ copy[i] = new boolean[array[i].length];
+ System.arraycopy(array[i], 0, copy[i], 0, array[i].length);
+ }
+ return copy;
+ }
+
+ /**
+ * Clones an array of ints
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static int[] getCopy(int[] array) {
+ if (array == null) {
+ return null;
+ }
+ int[] copy = new int[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+
+ /**
+ * Clones an array of ints
+ *
+ * @return null if input is null, otherwise return complete copy.
+ */
+ public static int[] getCopy(int[] array, int startingIndex) {
+ if (array == null) {
+ return null;
+ }
+ int[] copy = new int[array.length - startingIndex];
+ System.arraycopy(array, startingIndex, copy, 0, array.length - startingIndex);
+ return copy;
+ }
+
+ /**
+ * Copies all of source into dest - assumes dest to be large enough
+ */
+ public static void copy(double[][] source, double[][] dest) {
+ for (int i = 0; i < source.length; i++) {
+ System.arraycopy(source[i], 0, dest[i], 0, source[i].length);
+ }
+ }
+
+ /**
+ * A simple toString method for an array of doubles.
+ * No fancy formating.
+ * Puts spaces between each value
+ *
+ * @param number number of elements to process starting from first element
+ */
+ public static String toString(double[] array, int number) {
+ StringBuilder sb = new StringBuilder(array.length * 7);
+ for (int i = 0; i < number; i++) {
+ sb.append(array[i]);
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * A simple toString method for an array of objects.
+ * No fancy formating.
+ * Puts spaces between each value
+ *
+ * @param number number of elements to process starting from first element
+ */
+ public static String toString(Object[] array, int number) {
+ StringBuilder sb = new StringBuilder(array.length * 7);
+ for (int i = 0; i < number; i++) {
+ sb.append(array[i]);
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * A simple toString method for an array of objects.
+ * No fancy formating.
+ * Puts user defined string between each value
+ */
+ public static String toString(Object[] array, String divider) {
+ return toString(array, divider, array.length);
+ }
+
+ /**
+ * A simple toString method for an array of objects.
+ * No fancy formating.
+ * Puts user defined string between each value
+ *
+ * @param number number of elements to process starting from first element
+ */
+ public static String toString(Object[] array, String divider, int number) {
+ StringBuilder sb = new StringBuilder(array.length * 7);
+ for (int i = 0; i < number; i++) {
+ sb.append(array[i]);
+ if (i != number - 1) {
+ sb.append(divider);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * A simple toString method for an array of doubles.
+ * No fancy formating.
+ * Puts spaces between each value
+ */
+ public static String toString(Object[] array) {
+ return toString(array, array.length);
+ }
+
+ /**
+ * A simple toString method for an array of doubles.
+ * No fancy formating.
+ * Puts spaces between each value
+ */
+ public static String toString(double[] array) {
+ return toString(array, array.length);
+ }
+
+ /**
+ * A simple toString method for an array of ints.
+ * No fancy formating.
+ * Puts spaces between each value
+ */
+ public static String toString(int[] array) {
+ return toString(array, array.length);
+ }
+
+ public static String toString(int[] array, int number) {
+ StringBuilder sb = new StringBuilder(array.length * 7);
+ for (int i = 0; i < number; i++) {
+ sb.append(array[i]);
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * A simple toString method for an array of doubles.
+ * No fancy formating.
+ * Puts spaces between each value
+ */
+ public static String toString(double[][] array) {
+ String ss = "";
+ for (int i = 0; i < array.length; i++) {
+ ss += i + ":" + toString(array[i]) + '\n';
+ }
+ return ss;
+ }
+
+ /**
+ * A simple toString method for an array of ints.
+ * No fancy formating.
+ * Puts spaces between each value
+ */
+ public static String toString(int[][] array) {
+ String ss = "";
+ for (int i = 0; i < array.length; i++) {
+ ss += i + ":" + toString(array[i]) + '\n';
+ }
+ return ss;
+ }
+
+ /**
+ * Find the maximum "argument". if array is zero length returns -1
+ *
+ * @param array The array to examine
+ * @return the element of the array with the maximum value
+ */
+ public static int getArgmax(int[] array) {
+ if (array.length == 0) {
+ return -1;
+ }
+ int maxValue = array[0];
+ int maxIndex = 0;
+ for (int i = 1; i < array.length; i++) {
+ final int v = array[i];
+ if (v > maxValue) {
+ maxValue = v;
+ maxIndex = i;
+ }
+ }
+ return maxIndex;
+ }
+
+ /**
+ * Find the maximum "argument" (of a double array). if array is zero length returns -1
+ * @param array The array to examine
+ * @return the element of the array with the maximum value
+ */
+ public static int getArgmax(double[] array) {
+ if (array.length == 0) {
+ return -1;
+ }
+ double maxValue = array[0];
+ int maxIndex = 0;
+ for (int i = 1; i < array.length; i++) {
+ final double v = array[i];
+ if (v > maxValue) {
+ maxValue = v;
+ maxIndex = i;
+ }
+ }
+ return maxIndex;
+ }
+}
+
diff --git a/src/org/virion/jam/app/Arguments.java b/src/org/virion/jam/app/Arguments.java
new file mode 100644
index 0000000..025ce29
--- /dev/null
+++ b/src/org/virion/jam/app/Arguments.java
@@ -0,0 +1,514 @@
+/*
+ * Arguments.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package org.virion.jam.app;
+
+import java.util.StringTokenizer;
+
+public class Arguments {
+
+ public static final String ARGUMENT_CHARACTER = "-";
+
+ public static class ArgumentException extends Exception {
+ public ArgumentException() { super(); }
+ public ArgumentException(String message) { super(message); }
+ };
+
+ public static class Option {
+
+ public Option(String label, String description) {
+ this.label = label;
+ this.description = description;
+ }
+
+ String label;
+ String description;
+ boolean isAvailable = false;
+ };
+
+ public static class StringOption extends Option {
+
+ public StringOption(String label, String tag, String description) {
+ super(label, description);
+ this.tag = tag;
+ }
+
+ public StringOption(String label, String[] options, boolean caseSensitive, String description) {
+ super(label, description);
+ this.options = options;
+ this.caseSensitive = caseSensitive;
+ }
+ String[] options = null;
+ String tag = null;
+ boolean caseSensitive = false;
+
+ String value = null;
+ };
+
+ public static class IntegerOption extends Option {
+
+ public IntegerOption(String label, String description) {
+ super(label, description);
+ }
+
+ public IntegerOption(String label, int minValue, int maxValue, String description) {
+ super(label, description);
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+
+ int minValue = Integer.MIN_VALUE;
+ int maxValue = Integer.MAX_VALUE;
+
+ int value = 0;
+ };
+
+ public static class IntegerArrayOption extends IntegerOption {
+
+ public IntegerArrayOption(String label, String description) {
+ this(label, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, description);
+ }
+
+ public IntegerArrayOption(String label, int count, String description) {
+ this(label, count, Integer.MIN_VALUE, Integer.MAX_VALUE, description);
+ }
+
+ public IntegerArrayOption(String label, int minValue, int maxValue, String description) {
+ this(label, 0, minValue, maxValue, description);
+ }
+
+ public IntegerArrayOption(String label, int count, int minValue, int maxValue, String description) {
+ super(label, minValue, maxValue, description);
+ this.count = count; }
+
+ int count;
+
+ int[] values = null;
+ };
+
+ public static class RealOption extends Option {
+
+ public RealOption(String label, String description) {
+ super(label, description);
+ }
+
+ public RealOption(String label, double minValue, double maxValue, String description) {
+ super(label, description);
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+
+ double minValue = Double.NEGATIVE_INFINITY;
+ double maxValue = Double.POSITIVE_INFINITY;
+
+ double value = 0;
+ };
+
+ public static class RealArrayOption extends RealOption {
+
+ public RealArrayOption(String label, String description) {
+ this(label, 0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, description);
+ }
+
+ public RealArrayOption(String label, int count, String description) {
+ this(label, count, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, description);
+ }
+
+ public RealArrayOption(String label, double minValue, double maxValue, String description) {
+ this(label, 0, minValue, maxValue, description);
+ }
+
+ public RealArrayOption(String label, int count, double minValue, double maxValue, String description) {
+ super(label, minValue, maxValue, description);
+ this.count = count;
+ }
+
+ private int count;
+
+ double[] values = null;
+ };
+
+ /**
+ * Parse a list of arguments ready for accessing
+ */
+ public Arguments(Option[] options) {
+ this.options = options;
+ }
+
+ public Arguments(Option[] options, boolean caseSensitive) {
+ this.options = options;
+ this.caseSensitive = caseSensitive;
+ }
+
+ /**
+ * Parse a list of arguments ready for accessing
+ */
+ public void parseArguments(String[] arguments) throws ArgumentException {
+
+ int[] optionIndex = new int[arguments.length];
+ for (int i = 0; i < optionIndex.length; i++) {
+ optionIndex[i] = -1;
+ }
+
+ for (int i = 0; i < options.length; i++) {
+ Option option = options[i];
+
+ int index = findArgument(arguments, option.label);
+ if (index != -1) {
+
+ if (optionIndex[index] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+
+ // the first value may be appended to the option label (e.g., '-t1.0'):
+ String arg = arguments[index].substring(option.label.length() + 1);
+ optionIndex[index] = i;
+ option.isAvailable = true;
+
+ if (option instanceof IntegerArrayOption) {
+
+ IntegerArrayOption o = (IntegerArrayOption)option;
+ o.values = new int[o.count];
+ int k = index;
+ int j = 0;
+
+ while (j < o.count) {
+ if (arg.length() > 0) {
+ StringTokenizer tokenizer = new StringTokenizer(arg, ",\t ");
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (token.length() > 0) {
+ try {
+ o.values[j] = Integer.parseInt(token);
+ } catch (NumberFormatException nfe) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad integer value: " + token);
+ }
+ if (o.values[j] > o.maxValue || o.values[j] < o.minValue) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad integer value: " + token);
+ }
+ j++;
+ }
+ }
+ }
+
+ k++;
+
+ if (j < o.count) {
+ if (k >= arguments.length) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " is missing one or more values: expecting " + o.count + " integers");
+ }
+
+ if (optionIndex[k] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+
+ arg = arguments[k];
+ optionIndex[k] = i;
+ }
+ }
+ } else if (option instanceof IntegerOption) {
+
+ IntegerOption o = (IntegerOption)option;
+ if (arg.length() == 0) {
+ int k = index + 1;
+ if (k >= arguments.length) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " is missing its value: expecting an integer");
+ }
+
+ if (optionIndex[k] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+ arg = arguments[k];
+ optionIndex[k] = i;
+ }
+
+ try {
+ o.value = Integer.parseInt(arg);
+ } catch (NumberFormatException nfe) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad integer value: " + arg);
+ }
+ if (o.value > o.maxValue || o.value < o.minValue) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad integer value: " + arg);
+ }
+ } else if (option instanceof RealArrayOption) {
+
+ RealArrayOption o = (RealArrayOption)option;
+ o.values = new double[o.count];
+ int k = index;
+ int j = 0;
+
+ while (j < o.count) {
+ if (arg.length() > 0) {
+ StringTokenizer tokenizer = new StringTokenizer(arg, ",\t ");
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (token.length() > 0) {
+ try {
+ o.values[j] = Double.parseDouble(token);
+ } catch (NumberFormatException nfe) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad real value: " + token);
+ }
+ if (o.values[j] > o.maxValue || o.values[j] < o.minValue) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad real value: " + token);
+ }
+ j++;
+ }
+ }
+ }
+
+ k++;
+
+ if (j < o.count) {
+ if (k >= arguments.length) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " is missing one or more values: expecting " + o.count + " integers");
+ }
+
+ if (optionIndex[k] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+
+ arg = arguments[k];
+ optionIndex[k] = i;
+ }
+ }
+ } else if (option instanceof RealOption) {
+
+ RealOption o = (RealOption)option;
+ if (arg.length() == 0) {
+ int k = index + 1;
+ if (k >= arguments.length) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " is missing its value: expecting a real number");
+ }
+
+ if (optionIndex[k] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+ arg = arguments[k];
+ optionIndex[k] = i;
+ }
+
+ try {
+ o.value = Double.parseDouble(arg);
+ } catch (NumberFormatException nfe) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad real value: " + arg);
+ }
+ if (o.value > o.maxValue || o.value < o.minValue) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad real value: " + arg);
+ }
+ } else if (option instanceof StringOption) {
+
+ StringOption o = (StringOption)option;
+ if (arg.length() == 0) {
+ int k = index + 1;
+ if (k >= arguments.length) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " is missing its value: expecting a string");
+ }
+
+ if (optionIndex[k] != -1) {
+ throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument");
+ }
+ arg = arguments[k];
+ optionIndex[k] = i;
+ }
+
+ o.value = arg;
+
+ if (o.options != null) {
+ boolean found = false;
+ for (int j = 0; j < o.options.length; j++) {
+ if (o.options[j].equals(o.value)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new ArgumentException("Argument, " + arguments[index] +
+ " has a bad string value: " + arg);
+ }
+ }
+ } else { // is simply an Option - nothing to do...
+ }
+ }
+ }
+
+ int n = 0;
+ int i = arguments.length - 1;
+ while (i >= 0 && optionIndex[i] == -1 && !arguments[i].startsWith(ARGUMENT_CHARACTER)) {
+ n++;
+ i--;
+ }
+ leftoverArguments = new String[n];
+ for (i = 0; i < n; i++) {
+ leftoverArguments[i] = arguments[arguments.length - n + i];
+ }
+
+ for (i = 0; i < arguments.length - n; i++) {
+ if (optionIndex[i] == -1) {
+ throw new ArgumentException("Unrecognized argument: " + arguments[i]);
+ }
+ }
+
+ }
+
+ private int findArgument(String[] arguments, String label) {
+ for (int i = 0; i < arguments.length; i++) {
+
+ if (arguments[i].length() - 1 >= label.length()) {
+ if (arguments[i].startsWith(ARGUMENT_CHARACTER)) {
+ String l = arguments[i].substring(1, label.length() + 1);
+ if ((!caseSensitive && label.equalsIgnoreCase(l)) || label.equals(l)) {
+ return i;
+ }
+
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Does an argument with label exist?
+ */
+ public boolean hasOption(String label) {
+ int n = findOption(label);
+ if (n == -1) {
+ return false;
+ }
+
+ return options[n].isAvailable;
+ }
+
+ /**
+ * Return the value of an integer option
+ */
+ public int getIntegerOption(String label) {
+ IntegerOption o = (IntegerOption)options[findOption(label)];
+ return o.value;
+ }
+
+ /**
+ * Return the value of an integer array option
+ */
+ public int[] getIntegerArrayOption(String label) {
+ IntegerArrayOption o = (IntegerArrayOption)options[findOption(label)];
+ return o.values;
+ }
+
+ /**
+ * Return the value of an real number option
+ */
+ public double getRealOption(String label) {
+ RealOption o = (RealOption)options[findOption(label)];
+ return o.value;
+ }
+
+ /**
+ * Return the value of an real array option
+ */
+ public double[] getRealArrayOption(String label) {
+ RealArrayOption o = (RealArrayOption)options[findOption(label)];
+ return o.values;
+ }
+
+ /**
+ * Return the value of an string option
+ */
+ public String getStringOption(String label) {
+ StringOption o = (StringOption)options[findOption(label)];
+ return o.value;
+ }
+
+ /**
+ * Return any arguments leftover after the options
+ */
+ public String[] getLeftoverArguments() {
+ return leftoverArguments;
+ }
+
+ public void printUsage(String name, String commandLine) {
+
+ System.out.print(" Usage: " + name);
+ for (int i = 0; i < options.length; i++) {
+ Option option = options[i];
+ System.out.print(" [-" + option.label);
+
+ if (option instanceof IntegerArrayOption) {
+
+ IntegerArrayOption o = (IntegerArrayOption)option;
+ for (int j = 1; j <= o.count; j++) {
+ System.out.print(" <i" + j + ">");
+ }
+ System.out.print("]");
+ } else if (option instanceof IntegerOption) {
+
+ System.out.print(" <i>]");
+ } else if (option instanceof RealArrayOption) {
+
+ RealArrayOption o = (RealArrayOption)option;
+ for (int j = 1; j <= o.count; j++) {
+ System.out.print(" <r" + j + ">");
+ }
+ System.out.print("]");
+ } else if (option instanceof RealOption) {
+
+ System.out.print(" <r>]");
+ } else if (option instanceof StringOption) {
+
+ StringOption o = (StringOption)option;
+ if (o.options != null) {
+ System.out.print(" <" + o.options[0]);
+ for (int j = 1; j < o.options.length; j++) {
+ System.out.print("|" + o.options[j]);
+ }
+ System.out.print(">]");
+ } else {
+ System.out.print(" <" + o.tag + ">]");
+ }
+ } else {
+ System.out.print("]");
+ }
+ }
+ System.out.println(" " + commandLine);
+
+ for (int i = 0; i < options.length; i++) {
+ Option option = options[i];
+ System.out.println(" -" + option.label + " " + option.description);
+ }
+ }
+
+ private int findOption(String label) {
+ for (int i = 0; i < options.length; i++) {
+ String l = options[i].label;
+ if ((!caseSensitive && label.equalsIgnoreCase(l)) || label.equals(l)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private Option[] options = null;
+
+ private String[] leftoverArguments = null;
+
+ private boolean caseSensitive = false;
+}
+
diff --git a/src/org/virion/jam/app/Utils.java b/src/org/virion/jam/app/Utils.java
new file mode 100644
index 0000000..7fe1753
--- /dev/null
+++ b/src/org/virion/jam/app/Utils.java
@@ -0,0 +1,137 @@
+/*
+ * Utils.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+
+package org.virion.jam.app;
+
+import java.awt.*;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ *
+ * @author Alexei Drummond
+ * @author Andrew Rambaut
+ *
+ * @version $Id: Utils.java 527 2006-11-13 23:19:17Z matt_kearse $
+ */
+public class Utils {
+
+
+ private static javax.swing.JFileChooser SAVE_FILE_CHOOSER = null;
+
+ public static String getLoadFileName(String message) {
+ java.io.File file = getLoadFile(message);
+ if (file == null) return null;
+ return file.getAbsolutePath();
+ }
+
+ public static String getSaveFileName(String message) {
+ java.io.File file = getSaveFile(message);
+ if (file == null) return null;
+ return file.getAbsolutePath();
+ }
+
+ @SuppressWarnings({"deprecation"})
+ public static File getLoadFile(String message) {
+ // No file name in the arguments so throw up a dialog box...
+ java.awt.Frame frame = new java.awt.Frame();
+ java.awt.FileDialog chooser = new java.awt.FileDialog(frame, message,
+ java.awt.FileDialog.LOAD);
+ chooser.show();
+ if (chooser.getFile() == null) return null;
+ java.io.File file = new java.io.File(chooser.getDirectory(), chooser.getFile());
+ chooser.dispose();
+ frame.dispose();
+
+ return file;
+ }
+
+ public static File getSaveFile(String message) {
+ // No file name in the arguments so throw up a dialog box...
+ java.awt.Frame frame = new java.awt.Frame();
+ java.awt.FileDialog chooser = new java.awt.FileDialog(frame, message,
+ java.awt.FileDialog.SAVE);
+ chooser.setVisible(true);
+ java.io.File file = null;
+ if(chooser.getDirectory() != null && chooser.getFile() != null)
+ file = new java.io.File(chooser.getDirectory(), chooser.getFile());
+
+ chooser.dispose();
+ frame.dispose();
+
+ return file;
+ }
+
+ /**
+ * This function takes a file name and an array of extensions (specified
+ * without the leading '.'). If the file name ends with one of the extensions
+ * then it is returned with this trimmed off. Otherwise the file name is
+ * return as it is.
+ * @return the trimmed filename
+ */
+ public static String trimExtensions(String fileName, String[] extensions) {
+
+ String newName = null;
+
+ for (int i = 0; i < extensions.length; i++) {
+ String ext = "." + extensions[i];
+ if (fileName.endsWith(ext)) {
+ newName = fileName.substring(0, fileName.length() - ext.length());
+ }
+ }
+
+ if (newName == null) newName = fileName;
+
+ return newName;
+ }
+
+ /**
+ * @return a named image from file or resource bundle.
+ */
+ public static Image getImage(Object caller, String name) {
+
+ java.net.URL url = caller.getClass().getResource(name);
+ if (url != null) {
+ return Toolkit.getDefaultToolkit().createImage(url);
+ } else {
+ if (caller instanceof Component) {
+ Component c = (Component)caller;
+ Image i = c.createImage(100,20);
+ Graphics g = c.getGraphics();
+ g.drawString("Not found!", 1, 15);
+ return i;
+ } else return null;
+ }
+ }
+
+
+ /*public static void showExceptionDialog(JFrame parent, Throwable e, String title) {
+
+ JOptionPane pane = new JOptionPane(e.getMessage(), JOptionPane.ERROR_MESSAGE);
+ JDialog dialog = pane.createDialog(parent, title);
+ dialog.show();
+ }
+
+ public static void showExceptionDialog(JFrame parent, Throwable e) {
+
+ JExceptionDialog d = new JExceptionDialog(parent, true, "Exception", getStackTrace(e));
+ }*/
+
+ /**
+ * Return the stack trace of an exception as a string.
+ */
+ private static String getStackTrace(Throwable e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ return sw.toString();
+ }
+
+}
diff --git a/src/org/virion/jam/components/JVerticalLabel.java b/src/org/virion/jam/components/JVerticalLabel.java
new file mode 100644
index 0000000..151c359
--- /dev/null
+++ b/src/org/virion/jam/components/JVerticalLabel.java
@@ -0,0 +1,99 @@
+package org.virion.jam.components;
+
+import javax.swing.*;
+
+public class JVerticalLabel extends JLabel {
+ private boolean clockwise;
+
+ public JVerticalLabel(boolean clockwise) {
+ super();
+ this.clockwise = clockwise;
+ }
+
+ public JVerticalLabel(Icon image, boolean clockwise) {
+ super(image);
+ this.clockwise = clockwise;
+ }
+
+ public JVerticalLabel(Icon image, int horizontalAlignment, boolean clockwise) {
+ super(image, horizontalAlignment);
+ this.clockwise = clockwise;
+ }
+
+ public JVerticalLabel(String text, boolean clockwise) {
+ super(text);
+ this.clockwise = clockwise;
+ }
+
+ public JVerticalLabel(String text, Icon image, int horizontalAlignment, boolean clockwise) {
+ super(text, image, horizontalAlignment);
+ this.clockwise = clockwise;
+ }
+
+ public JVerticalLabel(String text, int horizontalAlignment, boolean clockwise) {
+ super(text, horizontalAlignment);
+ this.clockwise = clockwise;
+ }
+
+ public java.awt.Dimension getPreferredSize() {
+ java.awt.Insets ins = getInsets();
+ java.awt.FontMetrics fm = getFontMetrics(getFont());
+ String text = getText();
+ int h = fm.stringWidth(text), descent = fm.getDescent(),
+ ascent = fm.getAscent();
+ return new java.awt.Dimension(ins.top + ascent + descent + ins.bottom,
+ ins.right + h + ins.left);
+ }
+
+ public void paint(java.awt.Graphics g) {
+ java.awt.Graphics2D g2d = (java.awt.Graphics2D) g.create();
+
+ String text = getText();
+ java.awt.Dimension size = getSize();
+ java.awt.Insets ins = getInsets();
+
+ java.awt.FontMetrics fm = g2d.getFontMetrics(getFont());
+ int h = fm.stringWidth(text), x = ins.right;
+
+ switch (getHorizontalAlignment()) {
+ case SwingConstants.CENTER:
+ x = (size.height - h + ins.right - ins.left) / 2;
+ break;
+ case SwingConstants.TOP:
+ x = size.height - h - ins.left;
+ break;
+ }
+ int descent = fm.getDescent(), ascent = fm.getAscent(),
+ y = ins.top + ascent;
+ switch (getVerticalAlignment()) {
+ case SwingConstants.CENTER:
+ y = (size.width + ascent - descent + ins.top - ins.bottom) / 2;
+ break;
+ case SwingConstants.RIGHT:
+ y = size.width - descent - ins.bottom;
+ break;
+ }
+
+ java.awt.geom.AffineTransform trans;
+
+ if (clockwise) {
+ trans = new java.awt.geom.AffineTransform(0, 1, -1, 0, -size.height, 0);
+ } else {
+ trans = new java.awt.geom.AffineTransform(0, -1, 1, 0, 0, size.height);
+ }
+ g2d.transform(trans);
+ g2d.setPaintMode();
+ if (isOpaque() && (getBackground() != null)) {
+ g2d.setColor(getBackground());
+ g2d.fillRect(0, 0, size.height, size.width);
+ }
+ g2d.setFont(getFont());
+ g2d.setColor(getForeground());
+ g2d.drawString(text, x, y);
+ trans = null;
+ g2d = null;
+ }
+}
+
+
+
diff --git a/src/org/virion/jam/components/RealNumberField.java b/src/org/virion/jam/components/RealNumberField.java
new file mode 100644
index 0000000..4328ea3
--- /dev/null
+++ b/src/org/virion/jam/components/RealNumberField.java
@@ -0,0 +1,215 @@
+package org.virion.jam.components;
+
+/*
+* The contents of this file are subject to the BT "ZEUS" Open Source
+* Licence (L77741), Version 1.0 (the "Licence"); you may not use this file
+* except in compliance with the Licence. You may obtain a copy of the Licence
+* from $ZEUS_INSTALL/licence.html or alternatively from
+* http://www.labs.bt.com/projects/agents/zeus/licence.htm
+*
+* Except as stated in Clause 7 of the Licence, software distributed under the
+* Licence is distributed WITHOUT WARRANTY OF ANY KIND, either express or
+* implied. See the Licence for the specific language governing rights and
+* limitations under the Licence.
+*
+* The Original Code is within the package zeus.*.
+* The Initial Developer of the Original Code is British Telecommunications
+* public limited company, whose registered office is at 81 Newgate Street,
+* London, EC1A 7AJ, England. Portions created by British Telecommunications
+* public limited company are Copyright 1996-9. All Rights Reserved.
+*
+* THIS NOTICE MUST BE INCLUDED ON ANY COPY OF THIS FILE
+*/
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+
+public class RealNumberField extends JTextField
+ implements FocusListener, DocumentListener {
+
+ protected static char MINUS = '-';
+ protected static char PERIOD = '.';
+ protected EventListenerList changeListeners = new EventListenerList();
+ protected double min;
+ protected double max;
+ protected boolean range_check = false;
+ protected boolean range_checked = false;
+
+ public RealNumberField() {
+ super();
+ }
+
+ public RealNumberField(double min, double max) {
+ this();
+ this.min = min;
+ this.max = max;
+ range_check = true;
+ this.addFocusListener(this);
+ }
+
+ public void focusGained(FocusEvent evt) {
+ }
+
+ public void focusLost(FocusEvent evt) {
+ if (range_check && !range_checked) {
+ range_checked = true;
+ try {
+ double value = (Double.valueOf(getText())).doubleValue();
+ if (value < min || value > max) {
+ errorMsg();
+ return;
+ }
+ } catch (NumberFormatException e) {
+ errorMsg();
+ return;
+ }
+ }
+ }
+
+ public void setText(Double obj) {
+ setText(obj.toString());
+ }
+
+ public void setText(Integer obj) {
+ setText(obj.toString());
+ }
+
+ public void setText(Long obj) {
+ setText(obj.toString());
+ }
+
+ protected void errorMsg() {
+ JOptionPane.showMessageDialog(this,
+ "Illegal entry\nValue must be between " + min + " and " +
+ max + " inclusive", "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public void setRange(double min, double max) {
+ this.min = min;
+ this.max = max;
+ range_check = true;
+ }
+
+ public void setValue(double value) {
+ if (range_check) {
+ if (value < min || value > max) {
+ errorMsg();
+ return;
+ }
+ }
+ setText(Double.toString(value));
+ }
+
+ public Double getValue() {
+ try {
+ return new Double(getText());
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ public Double getValue(double def) {
+ try {
+ return new Double(getText());
+ } catch (NumberFormatException e) {
+ return new Double(def);
+ }
+ }
+
+ protected Document createDefaultModel() {
+ Document doc = new RealNumberFieldDocument();
+ doc.addDocumentListener(this);
+ return doc;
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ static char[] numberSet = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ class RealNumberFieldDocument extends PlainDocument {
+ public void insertString(int offs, String str, AttributeSet a)
+ throws BadLocationException {
+
+ if (str == null) return;
+ str = str.trim();
+
+ int length = getLength();
+ String buf = getText(0, offs) + str + getText(offs, length - offs);
+ buf = buf.trim();
+ char[] array = buf.toCharArray();
+
+ if (array.length > 0) {
+ if (array[0] != MINUS && !member(array[0], numberSet) &&
+ array[0] != PERIOD) {
+ Toolkit.getDefaultToolkit().beep();
+ return;
+ }
+ }
+
+ boolean period_found = (array.length > 0 && array[0] == PERIOD);
+
+ for (int i = 1; i < array.length; i++) {
+ if (!member(array[i], numberSet)) {
+ if (!period_found && array[i] == PERIOD) {
+ period_found = true;
+ } else {
+ Toolkit.getDefaultToolkit().beep();
+ return;
+ }
+ }
+ }
+ super.insertString(offs, str, a);
+ }
+ }
+
+ static boolean member(char item, char[] array) {
+ for (int i = 0; i < array.length; i++)
+ if (array[i] == item) return true;
+ return false;
+ }
+ //------------------------------------------------------------------------
+ // Event Methods
+ //------------------------------------------------------------------------
+
+ public void addChangeListener(ChangeListener x) {
+ changeListeners.add(ChangeListener.class, x);
+ }
+
+ public void removeChangeListener(ChangeListener x) {
+ changeListeners.remove(ChangeListener.class, x);
+ }
+
+ protected void fireChanged() {
+ ChangeEvent c = new ChangeEvent(this);
+ Object[] listeners = changeListeners.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ChangeListener cl = (ChangeListener) listeners[i + 1];
+ cl.stateChanged(c);
+ }
+ }
+ }
+}
diff --git a/src/org/virion/jam/components/WholeNumberField.java b/src/org/virion/jam/components/WholeNumberField.java
new file mode 100644
index 0000000..8561cbf
--- /dev/null
+++ b/src/org/virion/jam/components/WholeNumberField.java
@@ -0,0 +1,191 @@
+package org.virion.jam.components;
+
+/*
+* The contents of this file are subject to the BT "ZEUS" Open Source
+* Licence (L77741), Version 1.0 (the "Licence"); you may not use this file
+* except in compliance with the Licence. You may obtain a copy of the Licence
+* from $ZEUS_INSTALL/licence.html or alternatively from
+* http://www.labs.bt.com/projects/agents/zeus/licence.htm
+*
+* Except as stated in Clause 7 of the Licence, software distributed under the
+* Licence is distributed WITHOUT WARRANTY OF ANY KIND, either express or
+* implied. See the Licence for the specific language governing rights and
+* limitations under the Licence.
+*
+* The Original Code is within the package zeus.*.
+* The Initial Developer of the Original Code is British Telecommunications
+* public limited company, whose registered office is at 81 Newgate Street,
+* London, EC1A 7AJ, England. Portions created by British Telecommunications
+* public limited company are Copyright 1996-9. All Rights Reserved.
+*
+* THIS NOTICE MUST BE INCLUDED ON ANY COPY OF THIS FILE
+*/
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+
+public class WholeNumberField extends JTextField
+ implements FocusListener, DocumentListener {
+
+ protected static char MINUS_CHAR = '-';
+ protected EventListenerList changeListeners = new EventListenerList();
+ protected int min;
+ protected int max;
+ protected boolean range_check = false;
+ protected boolean range_checked = false;
+
+ public WholeNumberField() {
+ super();
+ }
+
+ public WholeNumberField(int min, int max) {
+ this();
+ this.min = min;
+ this.max = max;
+ range_check = true;
+ this.addFocusListener(this);
+ }
+
+ public void focusGained(FocusEvent evt) {
+ }
+
+ public void focusLost(FocusEvent evt) {
+ if (range_check && !range_checked) {
+ range_checked = true;
+ try {
+ int value = (Integer.valueOf(getText())).intValue();
+ if (value < min || value > max) {
+ errorMsg();
+ return;
+ }
+ } catch (NumberFormatException e) {
+ errorMsg();
+ return;
+ }
+ }
+ }
+
+ public void setText(Integer obj) {
+ setText(obj.toString());
+ }
+
+ protected void errorMsg() {
+ JOptionPane.showMessageDialog(this,
+ "Illegal entry\nValue must be between " + min + " and " +
+ max + " inclusive", "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public void setValue(int value) {
+ if (range_check) {
+ if (value < min || value > max) {
+ errorMsg();
+ return;
+ }
+ }
+ setText(Integer.toString(value));
+ }
+
+ public Integer getValue() {
+ try {
+ return new Integer(getText());
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ public Integer getValue(int default_value) {
+ Integer value = getValue();
+ if (value == null)
+ return new Integer(default_value);
+ else
+ return value;
+ }
+
+ protected Document createDefaultModel() {
+ Document doc = new WholeNumberFieldDocument();
+ doc.addDocumentListener(this);
+ return doc;
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ range_checked = false;
+ fireChanged();
+ }
+
+ static char[] numberSet = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ class WholeNumberFieldDocument extends PlainDocument {
+ public void insertString(int offs, String str, AttributeSet a)
+ throws BadLocationException {
+
+ if (str == null) return;
+ str = str.trim();
+
+ String buf = getText(0, offs) + str;
+ char[] array = buf.toCharArray();
+
+ if (array.length > 0) {
+ if (array[0] != MINUS_CHAR && !member(array[0], numberSet)) {
+ Toolkit.getDefaultToolkit().beep();
+ return;
+ }
+ }
+
+ for (int i = 1; i < array.length; i++) {
+ if (!member(array[i], numberSet)) {
+ Toolkit.getDefaultToolkit().beep();
+ return;
+ }
+ }
+ super.insertString(offs, str, a);
+ }
+ }
+
+ static boolean member(char item, char[] array) {
+ for (int i = 0; i < array.length; i++)
+ if (array[i] == item) return true;
+ return false;
+ }
+ //------------------------------------------------------------------------
+ // Event Methods
+ //------------------------------------------------------------------------
+
+ public void addChangeListener(ChangeListener x) {
+ changeListeners.add(ChangeListener.class, x);
+ }
+
+ public void removeChangeListener(ChangeListener x) {
+ changeListeners.remove(ChangeListener.class, x);
+ }
+
+ protected void fireChanged() {
+ ChangeEvent c = new ChangeEvent(this);
+ Object[] listeners = changeListeners.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ChangeListener cl = (ChangeListener) listeners[i + 1];
+ cl.stateChanged(c);
+ }
+ }
+ }
+}
diff --git a/src/org/virion/jam/console/ConsoleApplication.java b/src/org/virion/jam/console/ConsoleApplication.java
new file mode 100644
index 0000000..79612dd
--- /dev/null
+++ b/src/org/virion/jam/console/ConsoleApplication.java
@@ -0,0 +1,82 @@
+package org.virion.jam.console;
+
+import org.virion.jam.framework.Application;
+import org.virion.jam.framework.MenuBarFactory;
+import org.virion.jam.framework.DocumentFrame;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+
+public class ConsoleApplication extends Application {
+
+ private ConsoleFrame consoleFrame = null;
+ private boolean dontAskSave;
+
+ public ConsoleApplication(String nameString, String aboutString, Icon icon, boolean dontAskSave) throws IOException {
+ this(new ConsoleMenuBarFactory(), nameString, aboutString, icon, dontAskSave);
+ }
+
+ public ConsoleApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon, boolean dontAskSave) throws IOException {
+
+ super(menuBarFactory, nameString, aboutString, icon);
+
+ this.dontAskSave = dontAskSave;
+
+ consoleFrame = new ConsoleFrame();
+ consoleFrame.initialize();
+ consoleFrame.setVisible(true);
+
+ // event handling
+ consoleFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+ public void windowClosing(java.awt.event.WindowEvent e) {
+ thisWindowClosing(e);
+ }
+ });
+ }
+
+ public void initialize() {
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ // If this is a Mac application then register it at this point.
+ // This will result in any events such as open file being executed
+ // due to files being double-clicked or dragged on to the application.
+ org.virion.jam.mac.Utils.macOSXRegistration(this);
+ }
+ }
+
+ protected JFrame getDefaultFrame() { return consoleFrame; }
+
+ public DocumentFrame doNew() {
+ throw new RuntimeException("A ConsoleApplication cannot do a New command");
+ }
+
+ public DocumentFrame doOpenFile(File file) {
+ throw new RuntimeException("A ConsoleApplication cannot do an Open command");
+ }
+
+ public void doCloseWindow() {
+ doQuit();
+ }
+
+ public void doQuit() {
+ if (dontAskSave || consoleFrame.requestClose()) {
+
+ consoleFrame.setVisible(false);
+ consoleFrame.dispose();
+ System.exit(0);
+ }
+ }
+
+ public void doPreferences() {
+ }
+
+ public void doStop() {
+ doQuit();
+ }
+
+ // Close the window when the close box is clicked
+ private void thisWindowClosing(java.awt.event.WindowEvent e) {
+ doQuit();
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/console/ConsoleFrame.java b/src/org/virion/jam/console/ConsoleFrame.java
new file mode 100644
index 0000000..9242ae2
--- /dev/null
+++ b/src/org/virion/jam/console/ConsoleFrame.java
@@ -0,0 +1,157 @@
+/**
+ * ConsoleFrame.java
+ */
+
+package org.virion.jam.console;
+
+import org.virion.jam.framework.Application;
+import org.virion.jam.framework.DocumentFrame;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.*;
+
+public class ConsoleFrame extends DocumentFrame {
+ PipedInputStream piOut;
+ PipedInputStream piErr;
+ PipedOutputStream poOut;
+ PipedOutputStream poErr;
+ JTextArea textArea = new JTextArea();
+
+ public ConsoleFrame() throws IOException {
+
+ super();
+ // Set up System.out
+ piOut = new PipedInputStream();
+ poOut = new PipedOutputStream(piOut);
+ System.setOut(new PrintStream(poOut, true));
+
+ // Set up System.err
+ piErr = new PipedInputStream();
+ poErr = new PipedOutputStream(piErr);
+ System.setErr(new PrintStream(poErr, true));
+
+ textArea.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE ||
+ (e.getKeyCode() == KeyEvent.VK_C && e.isControlDown()) ||
+ (e.getKeyCode() == KeyEvent.VK_PERIOD && e.isMetaDown())) {
+ escapePressed();
+ }
+ }
+ });
+ }
+
+ protected void initializeComponents() {
+ // Add a scrolling text area
+ textArea.setEditable(false);
+ textArea.setRows( 25 );
+ textArea.setColumns( 80 );
+ textArea.setEditable(false);
+ textArea.setFont(new java.awt.Font("Monospaced", 0, 12));
+
+ JScrollPane scrollPane = new JScrollPane(textArea);
+ scrollPane.setVerticalScrollBarPolicy(javax.swing.JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ scrollPane.setHorizontalScrollBarPolicy(javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+ getContentPane().add(scrollPane, BorderLayout.CENTER);
+ pack();
+ setVisible(true);
+
+ // Create reader threads
+ new ReaderThread(piOut).start();
+ new ReaderThread(piErr).start();
+ }
+
+ protected boolean readFromFile(File file) throws FileNotFoundException, IOException {
+ throw new RuntimeException("Cannot read file");
+ }
+
+ protected boolean writeToFile(File file) throws IOException {
+
+ textArea.write(new FileWriter(file));
+ return true;
+ }
+
+ public JComponent getExportableComponent() {
+ return textArea;
+ }
+
+ protected void escapePressed() {
+ int option = JOptionPane.showConfirmDialog(this, "Are you sure you wish to stop?",
+ "Stop",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+
+ if (option == JOptionPane.OK_OPTION) {
+ ((ConsoleApplication)Application.getApplication()).doStop();
+ }
+ }
+
+ public void doCopy() {
+ textArea.copy();
+ }
+
+ public void doSelectAll() {
+ textArea.selectAll();
+ }
+
+ class ReaderThread extends Thread {
+ PipedInputStream pi;
+
+ ReaderThread(PipedInputStream pi) {
+ this.pi = pi;
+ }
+
+ public void run() {
+ try {
+ while (true) {
+ try {
+ Thread.sleep(100);
+ } catch(InterruptedException ie) {}
+ String input = "";
+ do {
+ int available = pi.available();
+ if (available == 0) break;
+ byte b[]=new byte[available];
+ pi.read(b);
+ input = input + new String(b);
+
+ } while( !input.endsWith("\n") && !input.endsWith("\r\n") );
+
+ if (input.length() > 0) {
+ SwingUtilities.invokeLater(new WriteText(input));
+ }
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ class WriteText implements Runnable {
+ String text;
+
+ WriteText(String text) {
+ this.text = text;
+ }
+
+ public void run() {
+ textArea.append(text);
+
+ // Make sure the last line is always visible
+ textArea.setCaretPosition(textArea.getDocument().getLength());
+
+ // Keep the text area down to a certain character size
+ int idealSize = 128000;
+ int maxExcess = 16000 ;
+ int excess = textArea.getDocument().getLength() - idealSize;
+ if (excess >= maxExcess) {
+ textArea.replaceRange("", 0, excess);
+ }
+
+ setDirty();
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/console/ConsoleMenuBarFactory.java b/src/org/virion/jam/console/ConsoleMenuBarFactory.java
new file mode 100644
index 0000000..d41d8d3
--- /dev/null
+++ b/src/org/virion/jam/console/ConsoleMenuBarFactory.java
@@ -0,0 +1,30 @@
+/**
+* ConsoleMenuBarFactory.java
+*/
+
+package org.virion.jam.console;
+
+import org.virion.jam.framework.*;
+import org.virion.jam.mac.MacFileMenuFactory;
+import org.virion.jam.mac.MacHelpMenuFactory;
+import org.virion.jam.mac.MacWindowMenuFactory;
+
+public class ConsoleMenuBarFactory extends DefaultMenuBarFactory {
+
+ public ConsoleMenuBarFactory() {
+ // org.virion stuff shouldn't be called from here - it's a separate project!
+
+ // no its not. This class is part of JAM.
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ //if (System.getProperty("mrj.version") != null) {
+ registerMenuFactory(new MacFileMenuFactory(false));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new MacWindowMenuFactory());
+ registerMenuFactory(new MacHelpMenuFactory());
+ } else {
+ registerMenuFactory(new DefaultFileMenuFactory(false));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new DefaultHelpMenuFactory());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/controlpalettes/AbstractController.java b/src/org/virion/jam/controlpalettes/AbstractController.java
new file mode 100644
index 0000000..b8247c4
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/AbstractController.java
@@ -0,0 +1,39 @@
+package org.virion.jam.controlpalettes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: AbstractController.java 363 2006-06-27 16:26:09Z rambaut $
+ */
+public abstract class AbstractController implements Controller {
+ /**
+ * Add a ControllerListener to this controllers list of listeners
+ * The main listener will be the ControlPalette itself which will use
+ * this to resize the panels if the components changed
+ *
+ * @param listener
+ */
+ public void addControllerListener(ControllerListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a listener
+ *
+ * @param listener
+ */
+ public void removeControllerListener(ControllerListener listener) {
+ listeners.remove(listener);
+ }
+
+ public void fireControllerChanged() {
+ for (ControllerListener listener : listeners) {
+ listener.controlsChanged();
+ }
+ }
+
+ private final List<ControllerListener> listeners = new ArrayList<ControllerListener>();
+}
diff --git a/src/org/virion/jam/controlpalettes/BasicControlPalette.java b/src/org/virion/jam/controlpalettes/BasicControlPalette.java
new file mode 100644
index 0000000..f9ac62b
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/BasicControlPalette.java
@@ -0,0 +1,249 @@
+package org.virion.jam.controlpalettes;
+
+import org.virion.jam.disclosure.DisclosureListener;
+import org.virion.jam.disclosure.DisclosurePanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: BasicControlPalette.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public class BasicControlPalette extends JPanel implements ControlPalette {
+
+ public final static int DEFAULT_OPENING_SPEED = 50;
+
+ public enum DisplayMode {
+ DEFAULT_OPEN,
+ INITIALLY_OPEN,
+ INITIALLY_CLOSED,
+ ONLY_ONE_OPEN
+ }
+
+ public BasicControlPalette(int preferredWidth) {
+ this(preferredWidth, DisplayMode.ONLY_ONE_OPEN, DEFAULT_OPENING_SPEED);
+ }
+
+ public BasicControlPalette(int preferredWidth, DisplayMode displayMode) {
+ this(preferredWidth, displayMode, DEFAULT_OPENING_SPEED);
+ }
+
+ public BasicControlPalette(int preferredWidth, DisplayMode displayMode, int openingSpeed) {
+ this.preferredWidth = preferredWidth;
+ this.displayMode = displayMode;
+ this.openingSpeed = openingSpeed;
+ BoxLayout layout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
+ setLayout(layout);
+ setOpaque(true);
+ }
+
+
+ public Dimension getPreferredSize() {
+ return new Dimension(preferredWidth, super.getPreferredSize().height);
+ }
+
+ public JPanel getPanel() {
+ return this;
+ }
+
+ private ControllerListener controllerListener = new ControllerListener() {
+ public void controlsChanged() {
+ layoutControls();
+ }
+ };
+
+ public void addController(Controller controller) {
+ controllers.add(controller);
+ controller.addControllerListener(controllerListener);
+ setupControls();
+ }
+
+ public void addController(int position, Controller controller) {
+ controllers.add(position, controller);
+ controller.addControllerListener(controllerListener);
+ setupControls();
+ }
+
+ public void removeController(Controller controller) {
+ controller.removeControllerListener(controllerListener);
+ controllers.remove(controller);
+ setupControls();
+ }
+
+ public int getControllerCount() {
+ return controllers.size();
+ }
+
+ public void fireControlsChanged() {
+ for (ControlPaletteListener listener : listeners) {
+ listener.controlsChanged();
+ }
+ }
+
+ public void addControlPaletteListener(ControlPaletteListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeControlPaletteListener(ControlPaletteListener listener) {
+ listeners.remove(listener);
+ }
+
+ private final List<ControlPaletteListener> listeners = new ArrayList<ControlPaletteListener>();
+
+ private void setupControls() {
+ removeAll();
+ disclosurePanels.clear();
+ controlsStates.clear();
+
+ for (Controller controller : controllers) {
+ add(Box.createVerticalStrut(1));
+ setupController(controller);
+ }
+ add(Box.createVerticalStrut(Integer.MAX_VALUE));
+ }
+
+ public void layoutControls() {
+ for (DisclosurePanel panel : disclosurePanels) {
+ panel.invalidate();
+ }
+ validate();
+ }
+
+ public void initialize() {
+ for (Controller controller : controllers) {
+ controller.initialize();
+ }
+ }
+
+ public void getSettings(Map<String,Object> settings) {
+ for (Controller controller : controllers) {
+ controller.getSettings(settings);
+ }
+ }
+
+ public void setSettings(Map<String,Object> settings) {
+ for (Controller controller : controllers) {
+ controller.setSettings(settings);
+ }
+ }
+
+ private void setupController(Controller controller) {
+
+ JPanel titlePanel = new JPanel(new BorderLayout(6, 0));
+ titlePanel.setOpaque(false);
+ titlePanel.add(controller.getTitleComponent(), BorderLayout.CENTER);
+
+ JPanel controllerPanel = controller.getPanel();
+ controllerPanel.setOpaque(false);
+
+ // This tells Quaqua L&F to use a small components (ignored otherwise)
+ controller.getTitleComponent().setFont(UIManager.getFont("SmallSystemFont"));
+ controller.getTitleComponent().setOpaque(false);
+
+// JCheckBox pinnedCheck = new JCheckBox();
+// pinnedCheck.setFocusPainted(false);
+//
+// pinnedCheck.setSelected(controller.isInitiallyVisible());
+// titlePanel.add(pinnedCheck, BorderLayout.EAST);
+ PinnedButton pinnedButton = new PinnedButton();
+
+ // This tells Quaqua L&F to use a small check box (ignored otherwise)
+ pinnedButton.setSelected(controller.isInitiallyVisible());
+ titlePanel.add(pinnedButton, BorderLayout.EAST);
+
+ final DisclosurePanel panel = new DisclosurePanel(
+ titlePanel, controllerPanel, controller.isInitiallyVisible(), openingSpeed);
+
+ if (displayMode == DisplayMode.ONLY_ONE_OPEN) {
+ panel.addDisclosureListener(new DisclosureListener() {
+ public void opening(Component component) {
+ }
+
+ public void opened(Component component) {
+ int newlyOpened = disclosurePanels.indexOf(component);
+ ControlsState controlsState = controlsStates.get(newlyOpened);
+
+ if (currentlyOpen >= 0) {
+ DisclosurePanel currentPanel = disclosurePanels.get(currentlyOpen);
+
+ ControlsState currentControls = controlsStates.get(currentlyOpen);
+ if (!currentControls.isPinned()) {
+ currentPanel.setOpen(false);
+ currentControls.setVisible(false);
+ }
+ }
+ currentlyOpen = newlyOpened;
+ controlsState.setVisible(true);
+ }
+
+ public void closing(Component component) {
+ }
+
+ public void closed(Component component) {
+ int newlyClosed = disclosurePanels.indexOf(component);
+ ControlsState controlsState = controlsStates.get(newlyClosed);
+ controlsState.setVisible(false);
+
+ if (newlyClosed == currentlyOpen) {
+ currentlyOpen = -1;
+ }
+ }
+ });
+ }
+
+ final ControlsState controlsState = new ControlsState(
+ controller.isInitiallyVisible(),
+ pinnedButton.isSelected());
+
+ pinnedButton.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ controlsState.setPinned(itemEvent.getStateChange() == ItemEvent.SELECTED);
+ }
+ });
+ disclosurePanels.add(panel);
+ controlsStates.add(controlsState);
+
+ panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(panel);
+ }
+
+ private int preferredWidth;
+ private DisplayMode displayMode;
+ private int openingSpeed = 50;
+ private int currentlyOpen = 0;
+ private List<Controller> controllers = new ArrayList<Controller>();
+ private List<DisclosurePanel> disclosurePanels = new ArrayList<DisclosurePanel>();
+ private List<ControlsState> controlsStates = new ArrayList<ControlsState>();
+
+ private class ControlsState {
+ ControlsState(boolean visible, boolean pinned) {
+ isVisible = visible;
+ isPinned = pinned;
+ }
+
+ boolean isVisible() {
+ return isVisible;
+ }
+
+ void setVisible(boolean visible) {
+ isVisible = visible;
+ }
+
+ boolean isPinned() {
+ return isPinned;
+ }
+
+ void setPinned(boolean pinned) {
+ isPinned = pinned;
+ }
+
+ private boolean isVisible;
+ private boolean isPinned;
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/controlpalettes/ControlPalette.java b/src/org/virion/jam/controlpalettes/ControlPalette.java
new file mode 100644
index 0000000..6640c09
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/ControlPalette.java
@@ -0,0 +1,65 @@
+package org.virion.jam.controlpalettes;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * Date: 20/03/2006
+ * Time: 10:23:21
+ *
+ * @author Joseph Heled
+ * @version $Id: ControlPalette.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public interface ControlPalette {
+
+ /**
+ * get the panel that encloses the control palette
+ * @return the panel
+ */
+ JPanel getPanel();
+
+ /**
+ * install a Controller into the palette
+ * @param controller
+ */
+ void addController(Controller controller);
+
+ /**
+ * tell listeners that the palette has changed
+ */
+ void fireControlsChanged();
+
+ /**
+ * Add a listener to this palette
+ * @param listener
+ */
+ void addControlPaletteListener(ControlPaletteListener listener);
+
+ /**
+ * Remove a listener fromm this palette
+ * @param listener
+ */
+ void removeControlPaletteListener(ControlPaletteListener listener);
+
+ /**
+ * Initialize all controllers when a new document is created. At this
+ * point, settings can be adjusted to match the contents of the document.
+ */
+ void initialize();
+
+ /**
+ * Gather up all the settings from all the controls in the palette.
+ * This would usually called before saving them with the document
+ * that the palette controls.
+ * @param settings
+ */
+ void getSettings(Map<String,Object> settings);
+
+ /**
+ * Distribute all the settings to all the controls in the palette.
+ * This would usually called after loading the document
+ * that the palette controls.
+ * @param settings
+ */
+ void setSettings(Map<String,Object> settings);
+}
diff --git a/src/org/virion/jam/controlpalettes/ControlPaletteListener.java b/src/org/virion/jam/controlpalettes/ControlPaletteListener.java
new file mode 100644
index 0000000..d174512
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/ControlPaletteListener.java
@@ -0,0 +1,10 @@
+package org.virion.jam.controlpalettes;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ControlPaletteListener.java 294 2006-04-14 10:28:11Z rambaut $
+ */
+public interface ControlPaletteListener {
+ void controlsChanged();
+}
diff --git a/src/org/virion/jam/controlpalettes/Controller.java b/src/org/virion/jam/controlpalettes/Controller.java
new file mode 100644
index 0000000..26b24f3
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/Controller.java
@@ -0,0 +1,66 @@
+package org.virion.jam.controlpalettes;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: Controller.java 485 2006-10-25 15:24:54Z rambaut $
+ */
+public interface Controller {
+
+ /**
+ * Get a component that will be put in the title bar of the palette section.
+ * If a simple text title is required, this should return a JLabel.
+ *
+ * @return A component
+ */
+ JComponent getTitleComponent();
+
+ /**
+ * Get a JPanel which is the main section for the palette.
+ * @return A panel
+ */
+ JPanel getPanel();
+
+ /**
+ * @return whether the panel should be open or closed initially
+ */
+ boolean isInitiallyVisible();
+
+ /**
+ * Initialize this controller when a new document is created. At this
+ * point, settings can be adjusted to match the contents of the document.
+ */
+ void initialize();
+
+ /**
+ * Collect the settings for this controller. These should be stored
+ * in the given settings map using string keys.
+ *
+ * @param settings the settings map
+ */
+ void getSettings(Map<String, Object> settings);
+
+ /**
+ * Set the settings for this controller. These will have been stored
+ * as a map by the getSettings function.
+ *
+ * @param settings the settings map
+ */
+ void setSettings(Map<String,Object> settings);
+
+ /**
+ * Add a ControllerListener to this controllers list of listeners
+ * The main listener will be the ControlPalette itself which will use
+ * this to resize the panels if the components changed
+ * @param listener the controller listener
+ */
+ void addControllerListener(ControllerListener listener);
+
+ /**
+ * Remove a listener
+ * @param listener the controller listener
+ */
+ void removeControllerListener(ControllerListener listener);
+}
diff --git a/src/org/virion/jam/controlpalettes/ControllerListener.java b/src/org/virion/jam/controlpalettes/ControllerListener.java
new file mode 100644
index 0000000..ab195e7
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/ControllerListener.java
@@ -0,0 +1,10 @@
+package org.virion.jam.controlpalettes;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ControllerListener.java 363 2006-06-27 16:26:09Z rambaut $
+ */
+public interface ControllerListener {
+ void controlsChanged();
+}
diff --git a/src/org/virion/jam/controlpalettes/PinnedButton.java b/src/org/virion/jam/controlpalettes/PinnedButton.java
new file mode 100644
index 0000000..9a2658d
--- /dev/null
+++ b/src/org/virion/jam/controlpalettes/PinnedButton.java
@@ -0,0 +1,65 @@
+package org.virion.jam.controlpalettes;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: PinnedButton.java 430 2006-08-26 19:19:18Z rambaut $
+ */
+public class PinnedButton extends JToggleButton {
+
+ public PinnedButton() {
+
+ putClientProperty("JButton.buttonType", "toolbar");
+ setBorderPainted(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ setContentAreaFilled(false);
+
+ setupIcon();
+ setRolloverEnabled(true);
+
+ addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // the button will already have changed state
+ setupIcon();
+ }
+ });
+ }
+
+ private void setupIcon() {
+ if (isSelected()) {
+ setIcon(PinnedButton.pinInIcon);
+ setRolloverIcon(pinInRolloverIcon);
+ } else {
+ setIcon(PinnedButton.pinOutIcon);
+ setRolloverIcon(pinOutRolloverIcon);
+ }
+ }
+
+ /**
+ * This overridden because when the button is programmatically selected,
+ * we want to skip the animation and jump straight to the final icon.
+ * @param isSelected
+ */
+ public void setSelected(boolean isSelected) {
+ super.setSelected(isSelected);
+ setupIcon();
+ }
+
+ private static Icon pinOutIcon = null;
+ private static Icon pinOutRolloverIcon = null;
+ private static Icon pinInIcon = null;
+ private static Icon pinInRolloverIcon = null;
+
+ static {
+ PinnedButton.pinOutIcon = IconUtils.getIcon(PinnedButton.class, "images/pinOut.png");
+ PinnedButton.pinOutRolloverIcon = IconUtils.getIcon(PinnedButton.class, "images/pinOutRollover.png");
+ PinnedButton.pinInIcon = IconUtils.getIcon(PinnedButton.class, "images/pinIn.png");
+ PinnedButton.pinInRolloverIcon = IconUtils.getIcon(PinnedButton.class, "images/pinInRollover.png");
+ }
+
+}
diff --git a/src/org/virion/jam/controlpalettes/images/pinIn.png b/src/org/virion/jam/controlpalettes/images/pinIn.png
new file mode 100644
index 0000000..6e24c6a
Binary files /dev/null and b/src/org/virion/jam/controlpalettes/images/pinIn.png differ
diff --git a/src/org/virion/jam/controlpalettes/images/pinInRollover.png b/src/org/virion/jam/controlpalettes/images/pinInRollover.png
new file mode 100644
index 0000000..b6ff628
Binary files /dev/null and b/src/org/virion/jam/controlpalettes/images/pinInRollover.png differ
diff --git a/src/org/virion/jam/controlpalettes/images/pinOut.png b/src/org/virion/jam/controlpalettes/images/pinOut.png
new file mode 100644
index 0000000..d2695b6
Binary files /dev/null and b/src/org/virion/jam/controlpalettes/images/pinOut.png differ
diff --git a/src/org/virion/jam/controlpalettes/images/pinOutRollover.png b/src/org/virion/jam/controlpalettes/images/pinOutRollover.png
new file mode 100644
index 0000000..a512a58
Binary files /dev/null and b/src/org/virion/jam/controlpalettes/images/pinOutRollover.png differ
diff --git a/src/org/virion/jam/controlpanels/BasicControlPalette.java b/src/org/virion/jam/controlpanels/BasicControlPalette.java
new file mode 100644
index 0000000..1a0f909
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/BasicControlPalette.java
@@ -0,0 +1,167 @@
+package org.virion.jam.controlpanels;
+
+import org.virion.jam.disclosure.DisclosureListener;
+import org.virion.jam.disclosure.DisclosurePanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: BasicControlPalette.java 276 2006-03-23 00:37:40Z pepster $
+ */
+public class BasicControlPalette extends JPanel implements ControlPalette {
+
+ public enum DisplayMode {
+ DEFAULT_OPEN,
+ INITIALLY_OPEN,
+ INITIALLY_CLOSED,
+ ONLY_ONE_OPEN
+ }
+
+ public BasicControlPalette(int preferredWidth) {
+ this(preferredWidth, DisplayMode.DEFAULT_OPEN, false);
+ }
+
+ /**
+ * todo We should probably dump this constructor. We don't really want a different constructor
+ * for each style that might be desired. The opening speed is OK, but I don't like passing a
+ * title colour. I suggest that properties might be used for this.
+ */
+ public BasicControlPalette(int preferredWidth, DisplayMode displayMode, boolean fastBlueStyle) {
+ this(preferredWidth, displayMode, fastBlueStyle ? 10 : 150);
+ this.titleColor = fastBlueStyle ? Color.BLUE : null;
+ }
+
+ public BasicControlPalette(int preferredWidth, DisplayMode displayMode, int openingSpeed) {
+ this.preferredWidth = preferredWidth;
+ this.displayMode = displayMode;
+ this.openingSpeed = openingSpeed;
+ BoxLayout layout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
+ setLayout(layout);
+ }
+
+
+ public Dimension getPreferredSize() {
+ return new Dimension(preferredWidth, super.getPreferredSize().height);
+ }
+
+ public JPanel getPanel() {
+ return this;
+ }
+
+ public void addControlsProvider(ControlsProvider provider, boolean addAtStart) {
+ provider.setControlPalette(this);
+ providers.add(addAtStart ? 0 : providers.size(), provider);
+ }
+
+ public void fireControlsChanged() {
+ for (ControlPaletteListener listener : listeners) {
+ listener.controlsChanged();
+ }
+ }
+
+ public void addControlPanelListener(ControlPaletteListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeControlPanelListener(ControlPaletteListener listener) {
+ listeners.remove(listener);
+ }
+
+ private final List<ControlPaletteListener> listeners = new ArrayList<ControlPaletteListener>();
+
+ public void setupControls() {
+ removeAll();
+ disclosurePanels.clear();
+ controlsList.clear();
+
+ for (ControlsProvider provider : providers) {
+ for (Controls controls : provider.getControls(false)) {
+ add(Box.createVerticalStrut(1));
+ addControls(controls);
+ }
+ }
+ add(Box.createVerticalStrut(Integer.MAX_VALUE));
+ }
+
+ private void addControls(final Controls controls) {
+
+ JPanel titlePanel = new JPanel(new BorderLayout(6, 0));
+ titlePanel.add(new JLabel(controls.getTitle()), BorderLayout.CENTER);
+
+ JCheckBox pinnedCheck = new JCheckBox();
+ pinnedCheck.setFocusPainted(false);
+
+ // This tells Quaqua L&F to use a small check box (ignored otherwise)
+ pinnedCheck.setFont(new Font("Lucida Grande", Font.PLAIN, 11));
+ pinnedCheck.setSelected(controls.isPinned());
+ titlePanel.add(pinnedCheck, BorderLayout.EAST);
+
+ final DisclosurePanel panel = new DisclosurePanel(
+ titlePanel, controls.getPanel(), controls.isVisible(), openingSpeed);
+
+ // @todo this is an ugly hack - see comment on Constructor
+ if (titleColor != null) {
+ panel.getTitleComponent().setForeground(titleColor);
+ }
+
+ if (displayMode == DisplayMode.ONLY_ONE_OPEN) {
+ panel.addDisclosureListener(new DisclosureListener() {
+ public void opening(Component component) {
+ }
+
+ public void opened(Component component) {
+ int newlyOpened = disclosurePanels.indexOf(component);
+ if (currentlyOpen >= 0) {
+ DisclosurePanel currentPanel = disclosurePanels.get(currentlyOpen);
+
+ Controls currentControls = controlsList.get(currentlyOpen);
+ if (!currentControls.isPinned()) {
+ currentPanel.setOpen(false);
+ currentControls.setVisible(false);
+ }
+ }
+ currentlyOpen = newlyOpened;
+ controls.setVisible(true);
+ }
+
+ public void closing(Component component) {
+ }
+
+ public void closed(Component component) {
+ controls.setVisible(false);
+ int newlyClosed = disclosurePanels.indexOf(component);
+ if (newlyClosed == currentlyOpen) {
+ currentlyOpen = -1;
+ }
+ }
+ });
+ }
+
+ pinnedCheck.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent itemEvent) {
+ controls.setPinned(itemEvent.getStateChange() == ItemEvent.SELECTED);
+ }
+ });
+ disclosurePanels.add(panel);
+ controlsList.add(controls);
+
+ panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(panel);
+ }
+
+ private int preferredWidth;
+ private DisplayMode displayMode;
+ private int openingSpeed = 50;
+ private Color titleColor = null;
+ private int currentlyOpen = 0;
+ private List<ControlsProvider> providers = new ArrayList<ControlsProvider>();
+ private List<DisclosurePanel> disclosurePanels = new ArrayList<DisclosurePanel>();
+ private List<Controls> controlsList = new ArrayList<Controls>();
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/controlpanels/ControlPalette.java b/src/org/virion/jam/controlpanels/ControlPalette.java
new file mode 100644
index 0000000..f4d1103
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/ControlPalette.java
@@ -0,0 +1,25 @@
+package org.virion.jam.controlpanels;
+
+import javax.swing.*;
+
+/**
+ * Date: 20/03/2006
+ * Time: 10:23:21
+ *
+ * @author Joseph Heled
+ * @version $Id: ControlPalette.java 482 2006-10-25 06:30:57Z twobeers $
+ */
+public interface ControlPalette {
+
+ JPanel getPanel();
+
+ void addControlsProvider(ControlsProvider provider, boolean addAtStart);
+
+ void fireControlsChanged();
+
+ void addControlPanelListener(ControlPaletteListener listener);
+
+ void removeControlPanelListener(ControlPaletteListener listener);
+
+ void setupControls();
+}
diff --git a/src/org/virion/jam/controlpanels/ControlPaletteListener.java b/src/org/virion/jam/controlpanels/ControlPaletteListener.java
new file mode 100644
index 0000000..f9592db
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/ControlPaletteListener.java
@@ -0,0 +1,10 @@
+package org.virion.jam.controlpanels;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: ControlPaletteListener.java 183 2006-01-23 21:29:48Z rambaut $
+ */
+public interface ControlPaletteListener {
+ void controlsChanged();
+}
diff --git a/src/org/virion/jam/controlpanels/Controls.java b/src/org/virion/jam/controlpanels/Controls.java
new file mode 100644
index 0000000..fdf44cf
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/Controls.java
@@ -0,0 +1,65 @@
+package org.virion.jam.controlpanels;
+
+import javax.swing.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: Controls.java 276 2006-03-23 00:37:40Z pepster $
+ */
+public class Controls {
+
+
+ public Controls(String title, JPanel panel, boolean isVisible) {
+ this(title, panel, isVisible, false, null);
+ }
+
+ /**
+ * @param title
+ * @param panel
+ * @param isVisible
+ * @param isPinned
+ * @param primaryCheckbox the "main" on/off toggle, if any.
+ */
+ public Controls(String title, JPanel panel, boolean isVisible, boolean isPinned, JCheckBox primaryCheckbox) {
+ this.title = title;
+ this.panel = panel;
+ this.isVisible = isVisible;
+ this.isPinned = isPinned;
+ this.primaryCheckbox = primaryCheckbox;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public JPanel getPanel() {
+ return panel;
+ }
+
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ public void setVisible(boolean visible) {
+ isVisible = visible;
+ }
+
+ public boolean isPinned() {
+ return isPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ isPinned = pinned;
+ }
+
+ public JCheckBox getPrimaryCheckbox() {
+ return primaryCheckbox;
+ }
+
+ private String title;
+ private JPanel panel;
+ private JCheckBox primaryCheckbox;
+ private boolean isVisible;
+
+ private boolean isPinned;
+}
diff --git a/src/org/virion/jam/controlpanels/ControlsProvider.java b/src/org/virion/jam/controlpanels/ControlsProvider.java
new file mode 100644
index 0000000..4608e08
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/ControlsProvider.java
@@ -0,0 +1,40 @@
+package org.virion.jam.controlpanels;
+
+import java.util.List;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: ControlsProvider.java 548 2006-11-30 01:43:31Z pepster $
+ */
+public interface ControlsProvider {
+ /**
+ * Give the controls provider with a handle for the controlPalette object.
+ *
+ * @param controlPalette
+ */
+ void setControlPalette(ControlPalette controlPalette);
+
+ /**
+ * Get a list of Controls handled by this provider.
+ *
+ * @param detachPrimaryCheckbox When false, do nothing. When true, if controls have a "main" on/off switch
+ * (implemented as a JCheckBox), that checkbox should not be included in the controls
+ * panel but returned in {@link Controls#getPrimaryCheckbox}
+ * @return A list of Controls
+ */
+ List<Controls> getControls(boolean detachPrimaryCheckbox);
+
+ /**
+ * Give the provider some settings.
+ *
+ * @param settings
+ */
+ void setSettings(ControlsSettings settings);
+
+ /**
+ * Get the settings for a given Controls object.
+ *
+ * @param settings
+ */
+ void getSettings(ControlsSettings settings);
+}
diff --git a/src/org/virion/jam/controlpanels/ControlsSettings.java b/src/org/virion/jam/controlpanels/ControlsSettings.java
new file mode 100644
index 0000000..9e2f904
--- /dev/null
+++ b/src/org/virion/jam/controlpanels/ControlsSettings.java
@@ -0,0 +1,12 @@
+package org.virion.jam.controlpanels;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: ControlsSettings.java 298 2006-04-16 09:00:35Z rambaut $
+ */
+public interface ControlsSettings {
+
+ void putSetting(String key, Object value);
+
+ Object getSetting(String key);
+}
diff --git a/src/org/virion/jam/demo/DemoApplication.java b/src/org/virion/jam/demo/DemoApplication.java
new file mode 100644
index 0000000..60ca63a
--- /dev/null
+++ b/src/org/virion/jam/demo/DemoApplication.java
@@ -0,0 +1,137 @@
+package org.virion.jam.demo;
+
+import org.virion.jam.framework.*;
+import org.virion.jam.util.IconUtils;
+import org.virion.jam.preferences.PreferencesSection;
+
+import javax.swing.*;
+import java.util.prefs.Preferences;
+import java.awt.*;
+
+public class DemoApplication extends MultiDocApplication {
+
+ public DemoApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon) {
+ super(menuBarFactory, nameString, aboutString, icon);
+
+ addPreferencesSection(new PreferencesSection() {
+ Icon projectToolIcon = IconUtils.getIcon(this.getClass(), "images/prefsGeneral.png");
+
+ public String getTitle() {
+ return "General";
+ }
+
+ public Icon getIcon() {
+ return projectToolIcon;
+ }
+
+ public JPanel getPanel() {
+ JPanel panel = new JPanel();
+ panel.add(generalCheck);
+ return panel;
+ }
+
+ public void retrievePreferences() {
+ Preferences prefs = Preferences.userNodeForPackage(DemoApplication.class);
+ generalCheck.setSelected(prefs.getBoolean("general_check", true));
+ }
+
+ public void storePreferences() {
+ Preferences prefs = Preferences.userNodeForPackage(DemoApplication.class);
+ prefs.putBoolean("general_check", generalCheck.isSelected());
+ }
+
+ JCheckBox generalCheck = new JCheckBox("General preference");
+ });
+
+ addPreferencesSection(new PreferencesSection() {
+ Icon projectToolIcon = IconUtils.getIcon(this.getClass(), "images/prefsAdvanced.png");
+
+
+ public String getTitle() {
+ return "Advanced";
+ }
+
+ public Icon getIcon() {
+ return projectToolIcon;
+ }
+
+ public JPanel getPanel() {
+ JPanel panel = new JPanel();
+ panel.add(new JCheckBox("Advanced preference"));
+ return panel;
+ }
+
+ public void retrievePreferences() {
+ Preferences prefs = Preferences.userNodeForPackage(DemoApplication.class);
+ advancedCheck.setSelected(prefs.getBoolean("advanced_check", true));
+ }
+
+ public void storePreferences() {
+ Preferences prefs = Preferences.userNodeForPackage(DemoApplication.class);
+ prefs.putBoolean("advanced_check", advancedCheck.isSelected());
+ }
+
+ JCheckBox advancedCheck = new JCheckBox("Advanced preference");
+ });
+ }
+
+ // Main entry point
+ static public void main(String[] args) {
+ System.setProperty("com.apple.macos.useScreenMenuBar","true");
+ System.setProperty("apple.laf.useScreenMenuBar","true");
+ System.setProperty("apple.awt.showGrowBox","true");
+ System.setProperty("apple.awt.antialiasing","on");
+ System.setProperty("apple.awt.textantialiasing","on");
+ System.setProperty("apple.awt.rendering","VALUE_RENDER_SPEED");
+
+ // set the Quaqua Look and Feel in the UIManager
+ try {
+ //System.setProperty("Quaqua.Debug.showClipBounds","true");
+ //System.setProperty("Quaqua.Debug.showVisualBounds","true");
+ UIManager.setLookAndFeel("ch.randelshofer.quaqua.QuaquaLookAndFeel");
+ // set UI manager properties here that affect Quaqua
+ UIManager.put("SystemFont", new Font("Lucida Grande", Font.PLAIN, 13));
+ UIManager.put("SmallSystemFont", new Font("Lucida Grande", Font.PLAIN, 11));
+ } catch (Exception e) {
+ try {
+
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ java.net.URL url = DemoApplication.class.getResource("/images/demo.png");
+ Icon icon = null;
+
+ if (url != null) {
+ icon = new ImageIcon(url);
+ }
+
+ String nameString = "JAM Demo";
+ String aboutString = "JAM Demo\nVersion 1.0\n \nCopyright 2006 Andrew Rambaut\nUniversity of Edinburgh\nAll Rights Reserved.";
+
+ DemoApplication app = new DemoApplication(new DemoMenuBarFactory(), nameString, aboutString, icon);
+
+ app.setDocumentFrameFactory(new DocumentFrameFactory() {
+ public DocumentFrame createDocumentFrame(Application app, MenuBarFactory menuBarFactory) {
+ return new DemoFrame("JAM Demo");
+ }
+ });
+
+ app.initialize();
+
+ if (args.length > 0) {
+ for (String arg : args) {
+ app.doOpen(arg);
+ }
+ }
+
+ if (app.getUpperDocumentFrame() == null) {
+ // If we haven't opened any files by now, prompt for one...
+ app.doOpen();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/demo/DemoFrame.java b/src/org/virion/jam/demo/DemoFrame.java
new file mode 100644
index 0000000..a628289
--- /dev/null
+++ b/src/org/virion/jam/demo/DemoFrame.java
@@ -0,0 +1,168 @@
+package org.virion.jam.demo;
+
+import org.virion.jam.demo.menus.DemoMenuHandler;
+import org.virion.jam.framework.DocumentFrame;
+import org.virion.jam.panels.*;
+import org.virion.jam.toolbar.*;
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.io.*;
+
+public class DemoFrame extends DocumentFrame implements DemoMenuHandler {
+
+ private StatusBar statusBar;
+
+ private SearchPanel filterPanel;
+ private JPopupMenu filterPopup;
+
+ public DemoFrame(String title) {
+ super();
+
+ setTitle(title);
+
+ getSaveAction().setEnabled(false);
+ getSaveAsAction().setEnabled(false);
+
+ getCutAction().setEnabled(false);
+ getCopyAction().setEnabled(false);
+ getPasteAction().setEnabled(false);
+ getDeleteAction().setEnabled(false);
+ getSelectAllAction().setEnabled(false);
+ getFindAction().setEnabled(true);
+
+ getZoomWindowAction().setEnabled(false);
+ }
+
+ public void initializeComponents() {
+
+ setSize(new java.awt.Dimension(1024, 768));
+
+ Toolbar toolBar = new Toolbar();
+
+ Icon infoToolIcon = IconUtils.getIcon(this.getClass(), "images/infoTool.png");
+
+ JButton infoToolButton = new ToolbarButton(
+ new ToolbarAction("Get Info", "Get Info...", infoToolIcon) {
+ public void actionPerformed(ActionEvent e){
+ // do something
+ }
+ });
+ toolBar.addComponent(infoToolButton);
+ infoToolButton.setEnabled(false);
+
+
+ toolBar.addSeparator();
+
+
+ toolBar.addFlexibleSpace();
+
+ filterPopup = new JPopupMenu();
+ filterPanel = new SearchPanel("Filter", filterPopup, true);
+
+ filterPanel.addSearchPanelListener(new SearchPanelListener() {
+
+ /**
+ * Called when the user requests a search by pressing return having
+ * typed a search string into the text field. If the continuousUpdate
+ * flag is true then this method is called when the user types into
+ * the text field.
+ *
+ * @param searchString the user's search string
+ */
+ public void searchStarted(String searchString) {
+ int index = filterPopup.getSelectionModel().getSelectedIndex();
+ if (index == -1) index = 0;
+
+ // do something
+ }
+
+ /**
+ * Called when the user presses the cancel search button or presses
+ * escape while the search is in focus.
+ */
+ public void searchStopped() {
+ }
+ });
+
+ JPanel panel3 = new JPanel(new FlowLayout());
+
+ panel3.add(filterPanel);
+
+ toolBar.addComponent(panel3);
+
+ statusBar = new StatusBar("");
+ statusBar.setStatusProvider(null);
+
+ JPanel topPanel = new JPanel(new BorderLayout(0,0));
+ topPanel.add(toolBar, BorderLayout.NORTH);
+ topPanel.add(statusBar, BorderLayout.CENTER);
+
+ getContentPane().setLayout(new java.awt.BorderLayout(0, 0));
+ getContentPane().add(topPanel, BorderLayout.NORTH);
+
+ JList list1 = new JList();
+ list1.setBorder(BorderFactory.createEmptyBorder());
+
+ JSplitPane splitPane1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, list1, new JPanel());
+ splitPane1.setBorder(BorderFactory.createEmptyBorder());
+ splitPane1.setDividerLocation(240);
+ splitPane1.setContinuousLayout(true);
+ splitPane1.putClientProperty("Quaqua.SplitPane.style","bar");
+
+ JList list = new JList();
+ list.setBorder(BorderFactory.createEmptyBorder());
+ list.setBackground(new Color(231, 237, 246));
+
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, list, splitPane1);
+ splitPane.setBorder(BorderFactory.createEmptyBorder());
+ splitPane.setDividerLocation(120);
+ splitPane.setContinuousLayout(true);
+ splitPane.putClientProperty("Quaqua.SplitPane.style","bar");
+ splitPane.setDividerSize(1);
+
+ getContentPane().add(splitPane, BorderLayout.CENTER);
+ }
+
+ protected boolean readFromFile(File file) throws FileNotFoundException, IOException {
+
+ return false;
+ }
+
+ protected boolean writeToFile(File file) {
+ return false;
+ }
+
+ public void doCopy() {
+ }
+
+ public final void doFind() {
+ }
+
+ public final void doFindAgain() {
+ }
+
+ public JComponent getExportableComponent() {
+ return (JComponent)getContentPane();
+ }
+
+ public Action getFirstAction() {
+ return firstAction;
+ }
+
+ public Action getSecondAction() {
+ return secondAction;
+ }
+
+ private AbstractAction firstAction = new AbstractAction("First") {
+ public void actionPerformed(ActionEvent ae) {
+ }
+ };
+
+ private AbstractAction secondAction = new AbstractAction("Second") {
+ public void actionPerformed(ActionEvent ae) {
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/demo/DemoMenuBarFactory.java b/src/org/virion/jam/demo/DemoMenuBarFactory.java
new file mode 100644
index 0000000..ec5f271
--- /dev/null
+++ b/src/org/virion/jam/demo/DemoMenuBarFactory.java
@@ -0,0 +1,25 @@
+package org.virion.jam.demo;
+
+import org.virion.jam.framework.*;
+import org.virion.jam.mac.*;
+import org.virion.jam.demo.menus.DemoMenuFactory;
+
+
+public class DemoMenuBarFactory extends DefaultMenuBarFactory {
+
+ public DemoMenuBarFactory() {
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ registerMenuFactory(new MacFileMenuFactory(true));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new DemoMenuFactory());
+ registerMenuFactory(new MacWindowMenuFactory());
+ registerMenuFactory(new MacHelpMenuFactory());
+ } else {
+ registerMenuFactory(new DefaultFileMenuFactory(true));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new DemoMenuFactory());
+ registerMenuFactory(new DefaultHelpMenuFactory());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/demo/images/filterBackground.png b/src/org/virion/jam/demo/images/filterBackground.png
new file mode 100644
index 0000000..5b94167
Binary files /dev/null and b/src/org/virion/jam/demo/images/filterBackground.png differ
diff --git a/src/org/virion/jam/demo/images/find.png b/src/org/virion/jam/demo/images/find.png
new file mode 100644
index 0000000..c12cb06
Binary files /dev/null and b/src/org/virion/jam/demo/images/find.png differ
diff --git a/src/org/virion/jam/demo/images/infoTool.png b/src/org/virion/jam/demo/images/infoTool.png
new file mode 100644
index 0000000..649de12
Binary files /dev/null and b/src/org/virion/jam/demo/images/infoTool.png differ
diff --git a/src/org/virion/jam/demo/images/prefsAdvanced.png b/src/org/virion/jam/demo/images/prefsAdvanced.png
new file mode 100644
index 0000000..9a7f991
Binary files /dev/null and b/src/org/virion/jam/demo/images/prefsAdvanced.png differ
diff --git a/src/org/virion/jam/demo/images/prefsClipboard.png b/src/org/virion/jam/demo/images/prefsClipboard.png
new file mode 100644
index 0000000..547220b
Binary files /dev/null and b/src/org/virion/jam/demo/images/prefsClipboard.png differ
diff --git a/src/org/virion/jam/demo/images/prefsGeneral.png b/src/org/virion/jam/demo/images/prefsGeneral.png
new file mode 100644
index 0000000..ab271ef
Binary files /dev/null and b/src/org/virion/jam/demo/images/prefsGeneral.png differ
diff --git a/src/org/virion/jam/demo/images/projectTool.png b/src/org/virion/jam/demo/images/projectTool.png
new file mode 100644
index 0000000..9a7f991
Binary files /dev/null and b/src/org/virion/jam/demo/images/projectTool.png differ
diff --git a/src/org/virion/jam/demo/images/tableSelection.png b/src/org/virion/jam/demo/images/tableSelection.png
new file mode 100644
index 0000000..c9fb068
Binary files /dev/null and b/src/org/virion/jam/demo/images/tableSelection.png differ
diff --git a/src/org/virion/jam/demo/images/tableSelectionGray.png b/src/org/virion/jam/demo/images/tableSelectionGray.png
new file mode 100644
index 0000000..5fa06a7
Binary files /dev/null and b/src/org/virion/jam/demo/images/tableSelectionGray.png differ
diff --git a/src/org/virion/jam/demo/menus/DemoMenuFactory.java b/src/org/virion/jam/demo/menus/DemoMenuFactory.java
new file mode 100644
index 0000000..1d44f72
--- /dev/null
+++ b/src/org/virion/jam/demo/menus/DemoMenuFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005 Your Corporation. All Rights Reserved.
+ */
+package org.virion.jam.demo.menus;
+
+import org.virion.jam.framework.MenuFactory;
+import org.virion.jam.framework.AbstractFrame;
+
+import javax.swing.*;
+
+/**
+ * @author rambaut
+ * Date: Feb 24, 2005
+ * Time: 5:12:11 PM
+ */
+public class DemoMenuFactory implements MenuFactory {
+
+ public static final String FIRST = "First";
+ public static final String SECOND = "Second";
+
+ public String getMenuName() {
+ return "Demo";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+ JMenuItem item;
+
+ if (frame instanceof DemoMenuHandler) {
+ item = new JMenuItem(((DemoMenuHandler)frame).getFirstAction());
+ menu.add(item);
+
+ item = new JMenuItem(((DemoMenuHandler)frame).getSecondAction());
+ menu.add(item);
+ } else {
+ item = new JMenuItem(FIRST);
+ item.setEnabled(false);
+ menu.add(item);
+
+ item = new JMenuItem(SECOND);
+ item.setEnabled(false);
+ menu.add(item);
+ }
+
+ }
+
+ public int getPreferredAlignment() {
+ return LEFT;
+ }
+}
diff --git a/src/org/virion/jam/demo/menus/DemoMenuHandler.java b/src/org/virion/jam/demo/menus/DemoMenuHandler.java
new file mode 100644
index 0000000..8f49830
--- /dev/null
+++ b/src/org/virion/jam/demo/menus/DemoMenuHandler.java
@@ -0,0 +1,14 @@
+package org.virion.jam.demo.menus;
+
+import javax.swing.*;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: DemoMenuHandler.java 386 2006-07-17 21:35:49Z rambaut $
+ */
+public interface DemoMenuHandler {
+ Action getFirstAction();
+ Action getSecondAction();
+
+
+}
diff --git a/src/org/virion/jam/disclosure/DisclosureButton.java b/src/org/virion/jam/disclosure/DisclosureButton.java
new file mode 100644
index 0000000..7bfd43a
--- /dev/null
+++ b/src/org/virion/jam/disclosure/DisclosureButton.java
@@ -0,0 +1,173 @@
+package org.virion.jam.disclosure;
+
+import javax.swing.*;
+import javax.swing.Timer;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import org.virion.jam.util.IconUtils;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: DisclosureButton.java 430 2006-08-26 19:19:18Z rambaut $
+ */
+public class DisclosureButton extends JToggleButton {
+
+ private Timer timer = null;
+ private boolean opening;
+ private final List<DisclosureListener> listeners = new ArrayList<DisclosureListener>();
+ private int animationSpeed;
+
+ public DisclosureButton() {
+ this(150);
+ }
+ public DisclosureButton(int animationSpeed) {
+ this.animationSpeed = animationSpeed;
+ putClientProperty("JButton.buttonType", "toolbar");
+ setBorderPainted(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ setContentAreaFilled(false);
+
+ setupIcon();
+
+ addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // the button will already have changed state
+ opening = isSelected();
+ current = (opening ? 0: 2);
+ startAnimation();
+ }
+ });
+ }
+
+ public void addDisclosureListener(DisclosureListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeDisclosureListener(DisclosureListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void setupIcon() {
+ if (isSelected()) {
+ setIcon(downIcon);
+ //setSelectedIcon(downPressedIcon);
+ } else {
+ setIcon(rightIcon);
+ //setSelectedIcon(rightPressedIcon);
+ }
+ }
+
+ /**
+ * This overridden because when the button is programmatically selected,
+ * we want to skip the animation and jump straight to the final icon.
+ * @param isSelected
+ */
+ public void setSelected(boolean isSelected) {
+ super.setSelected(isSelected);
+ setupIcon();
+ }
+
+ private void startAnimation() {
+ if (disclosureIcons == null) return;
+
+ timer = new Timer(animationSpeed, listener);
+ timer.setCoalesce(false);
+ timer.start();
+ }
+
+ private void stopAnimation() {
+ if (timer == null) return;
+ timer.stop();
+ setupIcon();
+ }
+
+ private int current;
+ ActionListener listener = new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ if (opening) {
+ current++;
+ } else {
+ current--;
+ }
+ if (current < 0 || current >= disclosureIcons.length) {
+ stopAnimation();
+ return;
+ }
+ if (current == 1) {
+ if (opening) {
+ fireOpening();
+ } else {
+ fireClosing();
+ }
+ } else {
+ if (opening) {
+ fireOpened();
+ } else {
+ fireClosed();
+ }
+ }
+ setIcon(disclosureIcons[current]);
+ paintImmediately(0, 0, getWidth(), getHeight());
+ }
+ };
+
+ private void fireOpening() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).opening(this);
+ }
+ }
+
+ private void fireOpened() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).opened(this);
+ }
+ }
+
+ private void fireClosing() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).closing(this);
+ }
+ }
+
+ private void fireClosed() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).closed(this);
+ }
+ }
+
+ private static Icon[] disclosureIcons = null;
+ private static Icon rightIcon = null;
+ private static Icon rightPressedIcon = null;
+ private static Icon rightDownIcon = null;
+ private static Icon downIcon = null;
+ private static Icon downPressedIcon = null;
+
+ static {
+ try {
+ rightIcon = IconUtils.getIcon(DisclosureButton.class, "images/disclosureRightNormal.png");
+ rightPressedIcon = IconUtils.getIcon(DisclosureButton.class, "images/disclosureRightPressed.png");
+
+ rightDownIcon = IconUtils.getIcon(DisclosureButton.class, "images/disclosureRightDown.png");
+
+ downIcon = IconUtils.getIcon(DisclosureButton.class, "images/disclosureDownNormal.png");
+ downPressedIcon = IconUtils.getIcon(DisclosureButton.class, "images/disclosureDownPressed.png");
+
+ disclosureIcons = new Icon[3];
+
+ disclosureIcons[0] = rightPressedIcon;
+ disclosureIcons[1] = rightDownIcon;
+ disclosureIcons[2] = downPressedIcon;
+
+ } catch (Exception e) {
+ // no icons...
+ }
+ }
+
+}
diff --git a/src/org/virion/jam/disclosure/DisclosureListener.java b/src/org/virion/jam/disclosure/DisclosureListener.java
new file mode 100644
index 0000000..cc3ea5f
--- /dev/null
+++ b/src/org/virion/jam/disclosure/DisclosureListener.java
@@ -0,0 +1,16 @@
+package org.virion.jam.disclosure;
+
+import java.awt.*;
+
+/**
+ * @author rambaut
+ * Date: May 25, 2005
+ * Time: 11:17:04 PM
+ */
+public interface DisclosureListener {
+ void opening(Component component);
+ void opened(Component component);
+
+ void closing(Component component);
+ void closed(Component component);
+}
diff --git a/src/org/virion/jam/disclosure/DisclosurePanel.java b/src/org/virion/jam/disclosure/DisclosurePanel.java
new file mode 100644
index 0000000..4423ff2
--- /dev/null
+++ b/src/org/virion/jam/disclosure/DisclosurePanel.java
@@ -0,0 +1,167 @@
+package org.virion.jam.disclosure;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * @author Andrew Rambaut
+ * @version $Id: DisclosurePanel.java 564 2006-12-08 02:07:18Z richardmoir $
+ */
+public class DisclosurePanel extends JPanel {
+
+ /**
+ *
+ * @param title The title of the panel
+ * @param panel The contents of the panel
+ * @param isOpen Whether the panel should start open
+ */
+ public DisclosurePanel(final String title, final JPanel panel, boolean isOpen) {
+ this(new JLabel(title), panel, isOpen, 50);
+ }
+
+ /**
+ *
+ * @param titleComponent The component to use as the title of the panel
+ * @param panel The contents of the panel
+ * @param isOpen Whether the panel should start open
+ * @param openSpeed The opening speed in milliseconds
+ */
+ public DisclosurePanel(final JComponent titleComponent, final JPanel panel,
+ boolean isOpen, int openSpeed) {
+
+ setOpaque(false);
+
+ this.panel = panel;
+ panel.setOpaque(false);
+
+ setLayout(new BorderLayout());
+
+ button = new DisclosureButton(openSpeed);
+
+ this.titleComponent = titleComponent;
+ titleComponent.setOpaque(false);
+
+ JPanel panel1 = new JPanel(new BorderLayout(6, 0)) {
+ public void paint(Graphics graphics) {
+ graphics.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), null);
+ super.paint(graphics);
+ }
+ };
+ panel1.setOpaque(false);
+ panel1.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ button.doClick();
+ }
+ });
+
+ JPanel componentPanel = new JPanel(new BorderLayout(6, 0));
+ componentPanel.setOpaque(false);
+
+ componentPanel.add(button, BorderLayout.WEST);
+ componentPanel.add(titleComponent, BorderLayout.CENTER);
+ panel1.add(componentPanel, BorderLayout.WEST);
+
+ add(panel1, BorderLayout.NORTH);
+
+ add(panel, BorderLayout.CENTER);
+
+ button.setSelected(isOpen);
+ panel.setVisible(isOpen);
+
+ button.addDisclosureListener(new DisclosureListener() {
+ public void opening(Component component) {
+ fireOpening();
+ }
+
+ public void opened(Component component) {
+ panel.setVisible(true);
+ fireOpened();
+ }
+
+ public void closing(Component component) {
+ fireClosing();
+ }
+
+ public void closed(Component component) {
+ panel.setVisible(false);
+ fireClosed();
+ }
+ });
+ }
+
+ public void setOpen(boolean isOpen) {
+ button.setSelected(isOpen);
+ panel.setVisible(isOpen);
+ }
+
+ public void addDisclosureListener(DisclosureListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeDisclosureListener(DisclosureListener listener) {
+ listeners.remove(listener);
+ }
+
+ private void fireOpening() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).opening(this);
+ }
+ }
+
+ private void fireOpened() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).opened(this);
+ }
+ }
+
+ private void fireClosing() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).closing(this);
+ }
+ }
+
+ private void fireClosed() {
+ Iterator iter = listeners.iterator();
+ while (iter.hasNext()) {
+ ((DisclosureListener)iter.next()).closed(this);
+ }
+ }
+
+ public DisclosureButton getDisclosureButton() {
+ return button;
+ }
+
+ public Component getTitleComponent() {
+ return titleComponent;
+ }
+
+ public JPanel getContentsPanel() {
+ return panel;
+ }
+
+ private final DisclosureButton button;
+ private final Component titleComponent;
+ private final JPanel panel;
+ private final java.util.List listeners = new ArrayList();
+
+ private static BufferedImage backgroundImage = null;
+
+ static {
+ try {
+ backgroundImage = IconUtils.getBufferedImage(DisclosurePanel.class, "images/titleBackground.png");
+
+ } catch (Exception e) {
+ // no icons...
+ }
+ }
+}
diff --git a/src/org/virion/jam/disclosure/images/disclosureDownNormal.png b/src/org/virion/jam/disclosure/images/disclosureDownNormal.png
new file mode 100644
index 0000000..0c8e4e2
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/disclosureDownNormal.png differ
diff --git a/src/org/virion/jam/disclosure/images/disclosureDownPressed.png b/src/org/virion/jam/disclosure/images/disclosureDownPressed.png
new file mode 100644
index 0000000..b959ad0
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/disclosureDownPressed.png differ
diff --git a/src/org/virion/jam/disclosure/images/disclosureRightDown.png b/src/org/virion/jam/disclosure/images/disclosureRightDown.png
new file mode 100644
index 0000000..f135dd3
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/disclosureRightDown.png differ
diff --git a/src/org/virion/jam/disclosure/images/disclosureRightNormal.png b/src/org/virion/jam/disclosure/images/disclosureRightNormal.png
new file mode 100644
index 0000000..42b2a8e
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/disclosureRightNormal.png differ
diff --git a/src/org/virion/jam/disclosure/images/disclosureRightPressed.png b/src/org/virion/jam/disclosure/images/disclosureRightPressed.png
new file mode 100644
index 0000000..e5afc9d
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/disclosureRightPressed.png differ
diff --git a/src/org/virion/jam/disclosure/images/titleBackground.png b/src/org/virion/jam/disclosure/images/titleBackground.png
new file mode 100644
index 0000000..5b94167
Binary files /dev/null and b/src/org/virion/jam/disclosure/images/titleBackground.png differ
diff --git a/src/org/virion/jam/framework/AboutBox.java b/src/org/virion/jam/framework/AboutBox.java
new file mode 100644
index 0000000..58294ee
--- /dev/null
+++ b/src/org/virion/jam/framework/AboutBox.java
@@ -0,0 +1,119 @@
+package org.virion.jam.framework;
+
+import org.virion.jam.util.IconUtils;
+import org.virion.jam.util.Utils;
+import org.virion.jam.html.SimpleLinkListener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.StringTokenizer;
+
+public class AboutBox extends AbstractFrame {
+
+ /**
+ * Creates an AboutBox with a given title, message and icon
+ * and centers it over the parent component.
+ */
+ public AboutBox(String title, String message, Icon icon) {
+ super();
+
+ if (icon != null) {
+ setIconImage(IconUtils.getBufferedImageFromIcon(icon));
+ }
+
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent event) {
+ close();
+ }
+ });
+ addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.VK_ESCAPE
+ ||
+ event.getKeyCode() == KeyEvent.VK_ENTER) {
+ close();
+ }
+ }
+ });
+
+ JPanel contentsPanel = new JPanel(new GridBagLayout());
+ contentsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ contentsPanel.setBackground(Color.white);
+
+ JLabel iconLabel = new JLabel(icon, JLabel.CENTER);
+ JLabel titleLabel = new JLabel(title, JLabel.CENTER);
+
+ Font font = titleLabel.getFont();
+ titleLabel.setFont(font.deriveFont(16.0f).deriveFont(Font.BOLD));
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.insets = new Insets(5,5,5,5);
+ contentsPanel.add(iconLabel, c);
+ contentsPanel.add(titleLabel, c);
+
+ font = font.deriveFont(11.0f);
+
+ if (message.startsWith("<html>")) {
+ JEditorPane editorPane = new JEditorPane("text/html", message);
+ editorPane.setOpaque(false);
+ editorPane.setFont(font);
+ editorPane.setEditable(false);
+ editorPane.addHyperlinkListener(new SimpleLinkListener());
+ contentsPanel.add(editorPane, c);
+ } else {
+ StringTokenizer tokens = new StringTokenizer(message, "\n");
+ while (tokens.hasMoreElements()) {
+ String text = tokens.nextToken();
+ JLabel messageLabel = new JLabel(text, JLabel.CENTER);
+ messageLabel.setFont(font);
+ contentsPanel.add(messageLabel, c);
+ }
+ }
+
+
+ getSaveAction().setEnabled(false);
+ getSaveAsAction().setEnabled(false);
+ getPrintAction().setEnabled(false);
+ getPageSetupAction().setEnabled(false);
+
+ getCutAction().setEnabled(false);
+ getCopyAction().setEnabled(false);
+ getPasteAction().setEnabled(false);
+ getDeleteAction().setEnabled(false);
+ getSelectAllAction().setEnabled(false);
+ getFindAction().setEnabled(false);
+
+ getZoomWindowAction().setEnabled(false);
+ getMinimizeWindowAction().setEnabled(true);
+ getCloseWindowAction().setEnabled(true);
+
+ getContentPane().add(contentsPanel);
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ setResizable(false);
+ pack();
+ Utils.centerComponent(this, null);
+ }
+
+ /**
+ * Sets the visibility to false and disposes the frame.
+ */
+ public void close() {
+ setVisible(false);
+ dispose();
+ }
+
+ protected void initializeComponents() {
+ }
+
+ public boolean requestClose() {
+ return false;
+ }
+
+ public JComponent getExportableComponent() {
+ return null;
+ }
+}
+
+
diff --git a/src/org/virion/jam/framework/AbstractFrame.java b/src/org/virion/jam/framework/AbstractFrame.java
new file mode 100644
index 0000000..9ab98a1
--- /dev/null
+++ b/src/org/virion/jam/framework/AbstractFrame.java
@@ -0,0 +1,327 @@
+/**
+ * AbstractFrame.java
+ */
+
+package org.virion.jam.framework;
+
+import org.virion.jam.util.PrintUtilities;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.awt.*;
+
+public abstract class AbstractFrame extends JFrame implements Exportable {
+
+ private JMenuBar menuBar = null;
+ private boolean isDirty = false;
+
+ public AbstractFrame() {
+ }
+
+ public final void initialize() {
+ initializeComponents();
+ if (menuBar == null) {
+ menuBar = new JMenuBar();
+ if(Application.getMenuBarFactory() == null)
+ return;
+ Application.getMenuBarFactory().populateMenuBar(menuBar, this);
+ }
+ setJMenuBar(menuBar);
+ }
+
+ protected abstract void initializeComponents();
+
+ public final boolean isDirty() {
+ return isDirty;
+ }
+
+ public final void setDirty() {
+ getRootPane().putClientProperty("windowModified", Boolean.TRUE);
+ this.isDirty = true;
+ }
+
+ public final void clearDirty() {
+ getRootPane().putClientProperty("windowModified", Boolean.FALSE);
+ this.isDirty = false;
+ }
+
+ public abstract boolean requestClose();
+
+ public void doImport() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doExport() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public final void doPrint() {
+ doPrint(false);
+ }
+ public final void doPrint(final boolean scaleIfDoesntImplementPrintable) {
+ // create a separate thread to do this, since it locks up the CPU
+ // for about five seconds, and it looks incredibly ugly when the main window
+ // is only partially painted.
+/* Runnable runnable = new Runnable() {
+ public void run() {*/
+ final PrinterJob printJob = PrinterJob.getPrinterJob();
+
+ JComponent component = getExportableComponent();
+ if (component != null) {
+ if (component instanceof Printable) {
+
+ printJob.setPrintable((Printable) component);
+ if (printJob.printDialog()) {
+// RepaintManager.currentManager(component).paintDirtyRegions();
+ try {
+ printJob.print();
+ } catch (PrinterException pe) {
+ JOptionPane.showMessageDialog(AbstractFrame.this, "Printing error: " + pe,
+ "Error Printing",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ } else {
+ if (scaleIfDoesntImplementPrintable)
+ PrintUtilities.printComponentScaled(component);
+ else
+ PrintUtilities.printComponent(component);
+ }
+ } else {
+ JOptionPane.showMessageDialog(AbstractFrame.this, "Printing error: No panel provided to print",
+ "Error Printing",
+ JOptionPane.ERROR_MESSAGE);
+ }
+/* }
+ };
+ Thread thread = new Thread(runnable);
+ thread.setPriority(Thread.MIN_PRIORITY);
+ thread.start();*/
+
+ }
+
+ public void doCloseWindow() {
+ if (requestClose()) {
+ setVisible(false);
+ dispose();
+ }
+ }
+
+ public void doZoomWindow() {
+ // not supported by JDK1.3
+ if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
+ this.setExtendedState(JFrame.MAXIMIZED_BOTH);
+ }
+ }
+
+ public void doMinimizeWindow() {
+ // not supported by JDK1.3
+ if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.ICONIFIED)) {
+ this.setExtendedState(JFrame.ICONIFIED);
+ }
+ }
+
+ public void doCut() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doCopy() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doPaste() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doDelete() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doSelectAll() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public void doFind() {
+ throw new RuntimeException("Not implemented in AbstractFrame - this must be overridden");
+ }
+
+ public Action getNewAction() {
+ return Application.getApplication().getNewAction();
+ }
+
+ public Action getOpenAction() {
+ return Application.getApplication().getOpenAction();
+ }
+
+ public Action getPageSetupAction() {
+ return Application.getApplication().getPageSetupAction();
+ }
+
+ public Action getExitAction() {
+ return Application.getApplication().getExitAction();
+ }
+
+ public Action getCloseWindowAction() {
+ return closeWindowAction;
+ }
+
+ public Action getAboutAction() {
+ return Application.getApplication().getAboutAction();
+ }
+
+ public Action getPreferencesAction() {
+ return Application.getApplication().getPreferencesAction();
+ }
+
+ public Action getWebsiteAction() {
+ return Application.getApplication().getWebsiteAction();
+ }
+
+ public Action getSaveAction() {
+ return saveAction;
+ }
+
+ public Action getSaveAsAction() {
+ return saveAsAction;
+ }
+
+
+ public Action getZoomWindowAction() {
+ return zoomWindowAction;
+ }
+
+ public Action getMinimizeWindowAction() {
+ return minimizeWindowAction;
+ }
+
+ public void setImportAction(Action importAction) {
+ assert importAction != null: "Import Action already set for this frame";
+ this.importAction = importAction;
+ }
+
+ public void setExportAction(Action exportAction) {
+ assert exportAction != null: "Export Action already set for this frame";
+ this.exportAction = exportAction;
+ }
+
+ public Action getImportAction() {
+ return importAction;
+ }
+
+ public Action getExportAction() {
+ return exportAction;
+ }
+
+ public Action getPrintAction() {
+ return printAction;
+ }
+
+ public Action getCutAction() {
+ return cutAction;
+ }
+
+ public Action getCopyAction() {
+ return copyAction;
+ }
+
+ public Action getPasteAction() {
+ return pasteAction;
+ }
+
+ public Action getDeleteAction() {
+ return deleteAction;
+ }
+
+ public Action getSelectAllAction() {
+ return selectAllAction;
+ }
+
+ public Action getFindAction() {
+ return findAction;
+ }
+
+ /**
+ * override this to provide a document specific help menu item
+ */
+ public Action getHelpAction() {
+ return null;
+ }
+
+ private AbstractAction saveAction = new AbstractAction("Save") {
+ public void actionPerformed(ActionEvent ae) {
+ // Do nothing.. This is just a dummy action - getSaveAction should be overriden to provide a proper action
+ }
+ };
+
+ private AbstractAction saveAsAction = new AbstractAction("Save As...") {
+ public void actionPerformed(ActionEvent ae) {
+ // Do nothing.. This is just a dummy action - getSaveAction should be overriden to provide a proper action
+ }
+ };
+
+ private Action importAction = null;
+ private Action exportAction = null;
+
+ private AbstractAction printAction = new AbstractAction("Print...") {
+ public void actionPerformed(ActionEvent ae) {
+ doPrint();
+ }
+ };
+
+ protected AbstractAction closeWindowAction = new AbstractAction("Close") {
+ public void actionPerformed(ActionEvent ae) {
+ doCloseWindow();
+ }
+ };
+
+ private AbstractAction zoomWindowAction = new AbstractAction("Zoom Window") {
+ public void actionPerformed(ActionEvent ae) {
+ doZoomWindow();
+ }
+ };
+
+ private AbstractAction minimizeWindowAction = new AbstractAction("Minimize Window") {
+ public void actionPerformed(ActionEvent ae) {
+ doMinimizeWindow();
+ }
+ };
+
+ private AbstractAction cutAction = new AbstractAction("Cut") {
+ public void actionPerformed(ActionEvent ae) {
+ doCut();
+ }
+ };
+
+ private AbstractAction copyAction = new AbstractAction("Copy") {
+ public void actionPerformed(ActionEvent ae) {
+ doCopy();
+ }
+ };
+
+ private AbstractAction pasteAction = new AbstractAction("Paste") {
+ public void actionPerformed(ActionEvent ae) {
+ doPaste();
+ }
+ };
+
+ private AbstractAction deleteAction = new AbstractAction("Delete") {
+ public void actionPerformed(ActionEvent ae) {
+ doDelete();
+ }
+ };
+
+ private AbstractAction selectAllAction = new AbstractAction("Select All") {
+ public void actionPerformed(ActionEvent ae) {
+ doSelectAll();
+ }
+ };
+
+ private AbstractAction findAction = new AbstractAction("Find...") {
+ public void actionPerformed(ActionEvent ae) {
+ doFind();
+ }
+ };
+}
diff --git a/src/org/virion/jam/framework/Application.java b/src/org/virion/jam/framework/Application.java
new file mode 100644
index 0000000..d3645cf
--- /dev/null
+++ b/src/org/virion/jam/framework/Application.java
@@ -0,0 +1,304 @@
+/**
+ * Application.java
+ */
+
+package org.virion.jam.framework;
+
+import org.virion.jam.html.HTMLViewer;
+import org.virion.jam.preferences.PreferencesDialog;
+import org.virion.jam.preferences.PreferencesSection;
+import org.virion.jam.util.BrowserLauncher;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.print.PageFormat;
+import java.awt.print.PrinterJob;
+import java.io.*;
+
+/*
+ * @todo Implement a list of open windows
+ * @todo Implement the recent files menu (persistance)
+ */
+
+public abstract class Application {
+
+ private static MenuBarFactory menuBarFactory;
+ private static Icon icon;
+ private static String nameString;
+ private static String aboutString;
+ private static String websiteURLString;
+ private static String helpURLString;
+
+ private static Application application = null;
+
+ private JMenu recentFileMenu = null;
+
+ private PreferencesDialog preferencesDialog = null;
+
+ public static Application getApplication() {
+ return application;
+ }
+
+ public static MenuBarFactory getMenuBarFactory() {
+ return menuBarFactory;
+ }
+
+ public static Icon getIcon() {
+ return icon;
+ }
+
+ public static String getNameString() {
+ return nameString;
+ }
+
+ public static String getAboutString() {
+ return aboutString;
+ }
+
+ public static String getWebsiteURLString() {
+ return websiteURLString;
+ }
+
+ public static String getHelpURLString() {
+ return helpURLString;
+ }
+
+ public Application(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon) {
+ this(menuBarFactory, nameString, aboutString, icon, null, null);
+ }
+
+ public Application(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon,
+ String websiteURLString, String helpURLString) {
+
+ Application.menuBarFactory = menuBarFactory;
+ Application.nameString = nameString;
+ Application.aboutString = aboutString;
+ Application.websiteURLString = websiteURLString;
+ Application.helpURLString = helpURLString;
+ Application.icon = icon;
+
+ aboutAction = new AbstractAction("About " + nameString + "...") {
+ public void actionPerformed(ActionEvent ae) {
+ doAbout();
+ }
+ };
+
+ if (application != null) {
+ throw new RuntimeException("Only on instance of Application is allowed");
+ }
+ application = this;
+
+ preferencesDialog = new PreferencesDialog(getDefaultFrame());
+ }
+
+ public abstract void initialize();
+
+ public void addMenuFactory(MenuFactory menuFactory) {
+ getMenuBarFactory().registerMenuFactory(menuFactory);
+ }
+
+ private final static int MAX_RECENT_FILES = 20;
+
+ public JMenu getRecentFileMenu() {
+ if (recentFileMenu == null) {
+ recentFileMenu = new JMenu("Recent Files");
+
+ // LOAD from preferences here?
+ }
+ recentFileMenu.setEnabled(getOpenAction().isEnabled());
+ return recentFileMenu;
+ }
+
+ public void addRecentFile(File file) {
+
+ if (recentFileMenu != null) {
+ if (recentFileMenu.getItemCount() == MAX_RECENT_FILES) {
+ recentFileMenu.remove(MAX_RECENT_FILES - 1);
+ }
+ recentFileMenu.insert(new RecentFileAction(file), 0);
+
+ // WRITE to preferences here
+ }
+ }
+
+ private class RecentFileAction extends AbstractAction {
+ public RecentFileAction(File recentFile) {
+ super(recentFile.getName());
+ this.recentFile = recentFile;
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ doOpenFile(recentFile);
+ }
+
+ private final File recentFile;
+ }
+
+ protected abstract JFrame getDefaultFrame();
+
+ public void doAbout() {
+ AboutBox aboutBox = new AboutBox(getNameString(), getAboutString(), getIcon());
+ //aboutBox.initialize(); //causes about frame to have the menu system from the main frame.
+ aboutBox.setVisible(true);
+ }
+
+ public void doHelp() {
+ if (helpURLString != null) {
+ displayURL(helpURLString);
+ } else {
+ try {
+ InputStream in = getClass().getResourceAsStream("/help/application.help");
+ if (in == null) return;
+ Reader reader = new InputStreamReader(in);
+ StringWriter writer = new StringWriter();
+ int c;
+ while ((c = reader.read()) != -1) writer.write(c);
+ reader.close();
+ writer.close();
+ JFrame frame = new HTMLViewer(getNameString() + " Help", writer.toString());
+ frame.setVisible(true);
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ public void doWebsite() {
+ if (websiteURLString != null) {
+ displayURL(websiteURLString);
+ }
+ }
+
+ public void displayURL(String urlString) {
+ try {
+ BrowserLauncher.openURL(urlString);
+ } catch (IOException ioe) {
+ // do nothing
+ }
+ }
+
+ public void doPageSetup() {
+ PrinterJob printJob = PrinterJob.getPrinterJob();
+ printJob.pageDialog(new PageFormat());
+ }
+
+ public DocumentFrame doOpen() {
+ Frame frame = getDefaultFrame();
+ if (frame == null) {
+ frame = new JFrame();
+ }
+
+ FileDialog dialog = new FileDialog(frame,
+ "Open Document",
+ FileDialog.LOAD);
+ dialog.setVisible(true);
+ if (dialog.getFile() != null) {
+ File file = new File(dialog.getDirectory(), dialog.getFile());
+ DocumentFrame doc = doOpenFile(file);
+ addRecentFile(file);
+ return doc;
+ }
+ return null;
+ }
+
+ public DocumentFrame doOpen(String fileName) {
+ if (fileName != null && fileName.length() > 0) {
+ File file = new File(fileName);
+ DocumentFrame doc = doOpenFile(file);
+ addRecentFile(file);
+ return doc;
+ }
+ return null;
+ }
+
+ public abstract DocumentFrame doNew();
+
+ public abstract DocumentFrame doOpenFile(File file);
+
+ public abstract void doQuit();
+
+ public void doPreferences() {
+ preferencesDialog.showDialog();
+ }
+
+ public void addPreferencesSection(PreferencesSection preferencesSection) {
+ preferencesDialog.addSection(preferencesSection);
+ }
+
+ public Action getNewAction() {
+ return newAction;
+ }
+
+ public Action getOpenAction() {
+ return openAction;
+ }
+
+ public Action getPageSetupAction() {
+ return pageSetupAction;
+ }
+
+ public Action getExitAction() {
+ return exitAction;
+ }
+
+ public Action getAboutAction() {
+ return aboutAction;
+ }
+
+ public Action getPreferencesAction() {
+ return preferencesAction;
+ }
+
+ public Action getHelpAction() {
+ return helpAction;
+ }
+
+ public Action getWebsiteAction() {
+ return websiteAction;
+ }
+
+ protected AbstractAction newAction = new AbstractAction("New") {
+ public void actionPerformed(ActionEvent ae) {
+ doNew();
+ }
+ };
+
+ protected AbstractAction openAction = new AbstractAction("Open...") {
+ public void actionPerformed(ActionEvent ae) {
+ doOpen();
+ }
+ };
+
+ protected AbstractAction pageSetupAction = new AbstractAction("Page Setup...") {
+ public void actionPerformed(ActionEvent ae) {
+ doPageSetup();
+ }
+ };
+
+ protected AbstractAction exitAction = new AbstractAction("Exit") {
+ public void actionPerformed(ActionEvent ae) {
+ doQuit();
+ }
+ };
+
+ protected AbstractAction aboutAction = null;
+
+ protected AbstractAction preferencesAction = new AbstractAction("Preferences...") {
+ public void actionPerformed(ActionEvent ae) {
+ doPreferences();
+ }
+ };
+
+ protected AbstractAction helpAction = new AbstractAction("Help...") {
+ public void actionPerformed(ActionEvent ae) {
+ doHelp();
+ }
+ };
+
+ protected AbstractAction websiteAction = new AbstractAction("Website...") {
+ public void actionPerformed(ActionEvent ae) {
+ doWebsite();
+ }
+ };
+
+}
diff --git a/src/org/virion/jam/framework/AuxilaryFrame.java b/src/org/virion/jam/framework/AuxilaryFrame.java
new file mode 100644
index 0000000..1d4732d
--- /dev/null
+++ b/src/org/virion/jam/framework/AuxilaryFrame.java
@@ -0,0 +1,63 @@
+/**
+ * AuxilaryFrame.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+
+public class AuxilaryFrame extends AbstractFrame {
+
+ private DocumentFrame documentFrame;
+ private JPanel contentsPanel;
+
+ public AuxilaryFrame(DocumentFrame documentFrame) {
+ super();
+
+ this.documentFrame = documentFrame;
+ this.contentsPanel = null;
+ }
+
+ public AuxilaryFrame(DocumentFrame documentFrame,
+ JPanel contentsPanel) {
+ super();
+
+ this.documentFrame = documentFrame;
+ setContentsPanel(contentsPanel);
+ }
+
+ public void setContentsPanel(JPanel contentsPanel) {
+ this.contentsPanel = contentsPanel;
+ getContentPane().add(contentsPanel);
+ pack();
+ }
+
+ public DocumentFrame getDocumentFrame() {
+ return documentFrame;
+ }
+
+ protected void initializeComponents() {
+ }
+
+ public boolean requestClose() {
+ return true;
+ }
+
+ public JComponent getExportableComponent() {
+ return contentsPanel;
+ }
+
+ @SuppressWarnings({"deprecation"})
+ public void doCloseWindow() {
+ hide();
+ }
+
+ public Action getSaveAction() {
+ return documentFrame.getSaveAction();
+ }
+
+ public Action getSaveAsAction() {
+ return documentFrame.getSaveAsAction();
+ }
+
+}
diff --git a/src/org/virion/jam/framework/Command.java b/src/org/virion/jam/framework/Command.java
new file mode 100644
index 0000000..6599b0a
--- /dev/null
+++ b/src/org/virion/jam/framework/Command.java
@@ -0,0 +1,21 @@
+package org.virion.jam.framework;
+
+import javax.swing.*;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 10:29:38 AM
+ */
+public interface Command {
+
+ /**
+ * Returns the swing Action for this command
+ * @return the action object
+ */
+ Action getAction();
+
+ String getPreferredMenu();
+
+ String getPreferredPosition();
+}
diff --git a/src/org/virion/jam/framework/DefaultEditMenuFactory.java b/src/org/virion/jam/framework/DefaultEditMenuFactory.java
new file mode 100644
index 0000000..cd986d1
--- /dev/null
+++ b/src/org/virion/jam/framework/DefaultEditMenuFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2005 Biomatters LTD. All Rights Reserved.
+ */
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:01:40 AM
+ *
+ * @version $Id: DefaultEditMenuFactory.java 183 2006-01-23 21:29:48Z rambaut $
+ */
+public class DefaultEditMenuFactory implements MenuFactory {
+ public String getMenuName() {
+ return "Edit";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ JMenuItem item;
+
+ menu.setMnemonic('E');
+
+ item = new JMenuItem(frame.getCutAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getCopyAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getPasteAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getDeleteAction());
+ menu.add(item);
+
+ item = new JMenuItem(frame.getSelectAllAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ menu.addSeparator();
+
+ item = new JMenuItem(frame.getFindAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ }
+
+ public int getPreferredAlignment() {
+ return LEFT;
+ }
+}
diff --git a/src/org/virion/jam/framework/DefaultFileMenuFactory.java b/src/org/virion/jam/framework/DefaultFileMenuFactory.java
new file mode 100644
index 0000000..874b041
--- /dev/null
+++ b/src/org/virion/jam/framework/DefaultFileMenuFactory.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2005 Biomatters LTD. All Rights Reserved.
+ */
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:01:06 AM
+ */
+public class DefaultFileMenuFactory implements MenuFactory {
+
+ private final boolean isMultiDocument;
+
+ public DefaultFileMenuFactory(boolean isMultiDocument) {
+ this.isMultiDocument = isMultiDocument;
+ }
+
+ public String getMenuName() {
+ return "File";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ JMenuItem item;
+
+ Application application = Application.getApplication();
+ menu.setMnemonic('F');
+
+ if (isMultiDocument) {
+ item = new JMenuItem(application.getNewAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+
+ item = new JMenuItem(application.getOpenAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getSaveAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getSaveAsAction());
+ menu.add(item);
+
+ if (frame.getImportAction() != null || frame.getExportAction() != null) {
+ menu.addSeparator();
+
+ if (frame.getImportAction() != null) {
+ item = new JMenuItem(frame.getImportAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+
+ if (frame.getExportAction() != null) {
+ item = new JMenuItem(frame.getExportAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+ }
+
+ menu.addSeparator();
+
+ item = new JMenuItem(frame.getPrintAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(application.getPageSetupAction());
+ menu.add(item);
+
+ menu.addSeparator();
+
+ if (application.getRecentFileMenu() != null) {
+ JMenu subMenu = application.getRecentFileMenu();
+ menu.add(subMenu);
+
+ menu.addSeparator();
+ }
+
+ item = new JMenuItem(application.getExitAction());
+ menu.add(item);
+ }
+
+ public int getPreferredAlignment() {
+ return LEFT;
+ }
+}
diff --git a/src/org/virion/jam/framework/DefaultHelpMenuFactory.java b/src/org/virion/jam/framework/DefaultHelpMenuFactory.java
new file mode 100644
index 0000000..d7328c6
--- /dev/null
+++ b/src/org/virion/jam/framework/DefaultHelpMenuFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005 Biomatters LTD. All Rights Reserved.
+ */
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:02:20 AM
+ */
+public class DefaultHelpMenuFactory implements MenuFactory {
+ public String getMenuName() {
+ return "Help";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ menu.setMnemonic('H');
+
+ JMenuItem item;
+
+ Application application = Application.getApplication();
+
+ item = new JMenuItem(application.getAboutAction());
+ menu.add(item);
+
+ if (frame.getHelpAction() != null) {
+ item = new JMenuItem(frame.getHelpAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ menu.addSeparator();
+ }
+
+ if (application.getHelpAction() != null) {
+ item = new JMenuItem(application.getHelpAction());
+ menu.add(item);
+
+ menu.addSeparator();
+ }
+
+ if (application.getWebsiteAction() != null) {
+ item = new JMenuItem("Website");
+ }
+ }
+
+ public int getPreferredAlignment() {
+ return RIGHT;
+ }
+}
diff --git a/src/org/virion/jam/framework/DefaultMenuBarFactory.java b/src/org/virion/jam/framework/DefaultMenuBarFactory.java
new file mode 100644
index 0000000..bd83d6a
--- /dev/null
+++ b/src/org/virion/jam/framework/DefaultMenuBarFactory.java
@@ -0,0 +1,97 @@
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 10:55:55 AM
+ */
+public class DefaultMenuBarFactory implements MenuBarFactory {
+
+ private final List<MenuFactory> menuFactories = new ArrayList<MenuFactory>();
+ private final List<MenuFactory> permanentMenuFactories = new ArrayList<MenuFactory>();
+ private boolean populatedMenu = false;
+ private JMenuBar menuBar = null;
+ AbstractFrame frame = null;
+
+ public final void populateMenuBar(JMenuBar menuBar, AbstractFrame frame) {
+ this.menuBar = menuBar;
+ this.frame = frame;
+ Map<String, JMenu> menus = new HashMap<String, JMenu>();
+ Map<Integer, String> order = new TreeMap<Integer, String>();
+
+ int leftOrder = 0;
+ int centerOrder = 1000;
+ int rightOrder = 10000;
+
+ List<MenuFactory> l = new ArrayList<MenuFactory>();
+ l.addAll(menuFactories);
+ l.addAll(permanentMenuFactories);
+
+ for (MenuFactory menuFactory : l) {
+ String name = menuFactory.getMenuName();
+ JMenu menu = (JMenu)menus.get(name);
+ if (menu == null) {
+ int alignment = menuFactory.getPreferredAlignment();
+ menu = new JMenu(name);
+ menus.put(name, menu);
+ switch (alignment) {
+ case MenuFactory.LEFT:
+ order.put(new Integer(leftOrder), name);
+ leftOrder++;
+ break;
+ case MenuFactory.CENTER:
+ order.put(new Integer(centerOrder), name);
+ centerOrder++;
+ break;
+ case MenuFactory.RIGHT:
+ order.put(new Integer(rightOrder), name);
+ rightOrder++;
+ break;
+ }
+ }
+
+ menuFactory.populateMenu(menu, frame);
+ }
+
+ for (int i : order.keySet()) {
+ String name = (String)order.get(i);
+ JMenu menu = (JMenu)menus.get(name);
+ menuBar.add(menu);
+ }
+ populatedMenu = true;
+ }
+
+ public final void deregisterMenuFactories() {
+ menuFactories.removeAll(menuFactories);
+ }
+
+ public final void registerPermanentMenuFactory(MenuFactory menuFactory) {
+ permanentMenuFactories.add(menuFactory);
+ if(populatedMenu) {
+ menuBar.removeAll();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ populateMenuBar(menuBar, frame);
+ frame.setJMenuBar(menuBar);
+ }
+ });
+ }
+ }
+
+ public final void registerMenuFactory(MenuFactory menuFactory) {
+ menuFactories.add(menuFactory);
+ if(populatedMenu) {
+ menuBar.removeAll();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ populateMenuBar(menuBar, frame);
+ frame.setJMenuBar(menuBar);
+ }
+ });
+ }
+ }
+
+}
diff --git a/src/org/virion/jam/framework/DocumentFrame.java b/src/org/virion/jam/framework/DocumentFrame.java
new file mode 100644
index 0000000..98e6118
--- /dev/null
+++ b/src/org/virion/jam/framework/DocumentFrame.java
@@ -0,0 +1,159 @@
+/**
+ * DocumentFrame.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public abstract class DocumentFrame extends AbstractFrame {
+
+ private File documentFile = null;
+
+ public DocumentFrame() {
+ super();
+ }
+
+ protected abstract void initializeComponents();
+
+ protected abstract boolean readFromFile(File file) throws IOException;
+
+ protected abstract boolean writeToFile(File file) throws IOException;
+
+ public final boolean hasFile() {
+ return documentFile != null;
+ }
+
+ public final File getFile() {
+ return documentFile;
+ }
+
+ public final void clearFile() {
+ documentFile = null;
+ }
+
+ public boolean requestClose() {
+ if (isDirty()) {
+ int option = JOptionPane.showConfirmDialog(this, "Do you wish to save?",
+ "Unsaved changes",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+
+ if (option == JOptionPane.YES_OPTION) {
+ return !doSave();
+ } else if (option == JOptionPane.CANCEL_OPTION || option == -1) {
+ return false;
+ }
+ return true;
+ }
+ return true;
+ }
+
+ public boolean openFile(File file) {
+
+ try {
+ documentFile = file;
+ if (readFromFile(file)) {
+ clearDirty();
+ setFrameTitle();
+
+ return true;
+ }
+ } catch (FileNotFoundException fnfe) {
+ JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
+ "Unable to open file",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IOException ioe) {
+ JOptionPane.showMessageDialog(this, "Unable to read file: " + ioe,
+ "Unable to read file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ return false;
+ }
+
+ public boolean doSave() {
+ if (!hasFile()) {
+ return doSaveAs();
+ } else {
+ try {
+ if (writeToFile(documentFile)) {
+ clearDirty();
+
+ return true;
+ }
+ } catch (IOException ioe) {
+ JOptionPane.showMessageDialog(this, "Unable to save file: " + ioe,
+ "Unable to save file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ return false;
+ }
+
+ public boolean doSaveAs() {
+ FileDialog dialog = new FileDialog(this,
+ "Save Document As...",
+ FileDialog.SAVE);
+
+ dialog.setVisible(true);
+ if (dialog.getFile() == null) {
+ // the dialog was cancelled...
+ return false;
+ }
+
+ File file = new File(dialog.getDirectory(), dialog.getFile());
+
+ try {
+ if (writeToFile(file)) {
+
+ clearDirty();
+ documentFile = file;
+ setFrameTitle();
+
+ return true;
+ }
+ } catch (IOException ioe) {
+ JOptionPane.showMessageDialog(this, "Unable to save file: " + ioe,
+ "Unable to save file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ return false;
+ }
+
+ protected final void setFrameTitle() {
+ String title = getTitle();
+ if (documentFile != null) {
+ setTitle(title + " - " + documentFile.getName());
+ }
+
+ }
+
+ public Action getSaveAction() {
+ return saveAction;
+ }
+
+ public Action getSaveAsAction() {
+ return saveAsAction;
+ }
+
+ private AbstractAction saveAction = new AbstractAction("Save") {
+ public void actionPerformed(ActionEvent ae) {
+ doSave();
+ }
+ };
+
+ private AbstractAction saveAsAction = new AbstractAction("Save As...") {
+ public void actionPerformed(ActionEvent ae) {
+ doSaveAs();
+ }
+ };
+
+}
diff --git a/src/org/virion/jam/framework/DocumentFrameFactory.java b/src/org/virion/jam/framework/DocumentFrameFactory.java
new file mode 100644
index 0000000..5ade9cc
--- /dev/null
+++ b/src/org/virion/jam/framework/DocumentFrameFactory.java
@@ -0,0 +1,12 @@
+/**
+ * DocumentFrameFactory.java
+ */
+
+package org.virion.jam.framework;
+
+
+
+public interface DocumentFrameFactory {
+
+ DocumentFrame createDocumentFrame(Application app, MenuBarFactory menuBarFactory);
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/Exportable.java b/src/org/virion/jam/framework/Exportable.java
new file mode 100644
index 0000000..418618a
--- /dev/null
+++ b/src/org/virion/jam/framework/Exportable.java
@@ -0,0 +1,12 @@
+/**
+ * Exportable.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+
+public interface Exportable {
+
+ JComponent getExportableComponent();
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/MenuBarFactory.java b/src/org/virion/jam/framework/MenuBarFactory.java
new file mode 100644
index 0000000..fbde65d
--- /dev/null
+++ b/src/org/virion/jam/framework/MenuBarFactory.java
@@ -0,0 +1,18 @@
+/**
+ * MenuBarFactory.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.awt.*;
+
+public interface MenuBarFactory {
+
+ public final static int MENU_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
+
+ void populateMenuBar(JMenuBar menuBar, AbstractFrame frame);
+ void deregisterMenuFactories();
+ void registerPermanentMenuFactory(MenuFactory menuFactory);
+ void registerMenuFactory(MenuFactory menuFactory);
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/MenuFactory.java b/src/org/virion/jam/framework/MenuFactory.java
new file mode 100644
index 0000000..cd8e2ea
--- /dev/null
+++ b/src/org/virion/jam/framework/MenuFactory.java
@@ -0,0 +1,36 @@
+/**
+ * MenuBarFactory.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+
+public interface MenuFactory {
+
+ public final static int LEFT = 0;
+ public final static int CENTER = 1;
+ public final static int RIGHT = 2;
+
+ /**
+ * Give the name of this menu. If multiple MenuFactories are
+ * registered with the same name, then these will be appended
+ * into a single actual menu.
+ */
+ String getMenuName();
+
+ /**
+ * This method should populate the menu with menu items. Reference
+ * can be made to the frame in order to get Actions.
+ * @param menu
+ * @param frame
+ */
+ void populateMenu(JMenu menu, AbstractFrame frame);
+
+ /**
+ * Returns the preferred alignment of the menu in the menu bar. This
+ * should be one of MenuFactory.LEFT, MenuFactory.CENTER or MenuFactory.RIGHT.
+ * @return the alignment
+ */
+ int getPreferredAlignment();
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/MultiDocApplication.java b/src/org/virion/jam/framework/MultiDocApplication.java
new file mode 100644
index 0000000..621e9ab
--- /dev/null
+++ b/src/org/virion/jam/framework/MultiDocApplication.java
@@ -0,0 +1,231 @@
+/**
+ * MultiDocApplication.java
+ */
+
+package org.virion.jam.framework;
+
+import org.virion.jam.mac.Utils;
+
+import javax.swing.*;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+public class MultiDocApplication extends Application {
+ private DocumentFrameFactory documentFrameFactory = null;
+
+ private AbstractFrame invisibleFrame = null;
+ private DocumentFrame upperDocumentFrame = null;
+
+ private ArrayList<DocumentFrame> documents = new ArrayList<DocumentFrame>();
+
+ public MultiDocApplication(String nameString, String aboutString, Icon icon) {
+
+ super(new MultiDocMenuBarFactory(), nameString, aboutString, icon);
+ }
+
+ public MultiDocApplication(String nameString, String aboutString, Icon icon,
+ String websiteURLString, String helpURLString) {
+
+ super(new MultiDocMenuBarFactory(), nameString, aboutString, icon, websiteURLString, helpURLString);
+ }
+
+ public MultiDocApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon) {
+
+ super(menuBarFactory, nameString, aboutString, icon);
+ }
+
+ public MultiDocApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon,
+ String websiteURLString, String helpURLString) {
+
+ super(menuBarFactory, nameString, aboutString, icon, websiteURLString, helpURLString);
+ }
+
+ public final void initialize() {
+ setupFramelessMenuBar();
+
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ // If this is a Mac application then register it at this point.
+ // This will result in any events such as open file being executed
+ // due to files being double-clicked or dragged on to the application.
+ org.virion.jam.mac.Utils.macOSXRegistration(this);
+ }
+ }
+
+ public void setDocumentFrameFactory(DocumentFrameFactory documentFrameFactory) {
+ this.documentFrameFactory = documentFrameFactory;
+ }
+
+ protected JFrame getDefaultFrame() {
+ JFrame frame = getUpperDocumentFrame();
+ if (frame == null) return invisibleFrame;
+ return frame;
+ }
+
+ protected DocumentFrame createDocumentFrame() {
+ return documentFrameFactory.createDocumentFrame(this, getMenuBarFactory());
+ }
+
+ public void destroyDocumentFrame(DocumentFrame documentFrame) {
+ closeDocumentFrame(documentFrame);
+ }
+
+ public DocumentFrame doNew() {
+ DocumentFrame doc = createDocumentFrame();
+ addDocumentFrame(doc);
+ return doc;
+ }
+
+ public DocumentFrame doOpenFile(File file) {
+
+ DocumentFrame documentFrame = getDocumentFrame(file);
+
+ if (documentFrame == null) {
+ documentFrame = createDocumentFrame();
+ if (documentFrame.openFile(file)) {
+ addDocumentFrame(documentFrame);
+ }
+ }
+
+ documentFrame.toFront();
+
+ return documentFrame;
+ }
+
+ public void doQuit() {
+
+ boolean ok = true;
+
+ while (documents.size() > 0) {
+ DocumentFrame documentFrame = documents.get(0);
+ if (!documentFrame.requestClose()) {
+ return;
+ } else {
+ documents.remove(documentFrame);
+ if (documentFrame.getWindowListeners().length > 0) {
+ documentFrame.removeWindowListener(documentFrame.getWindowListeners()[0]);
+ }
+ documentFrame.setVisible(false);
+ documentFrame.dispose();
+ }
+ }
+
+ if (ok) {
+ System.exit(0);
+ }
+ }
+
+ public DocumentFrame getUpperDocumentFrame() {
+ return upperDocumentFrame;
+ }
+
+ public DocumentFrame getDocumentFrame(File file) {
+ if (documents != null && documents.size() > 0) {
+ for (DocumentFrame doc : documents) {
+ if (doc != null && doc.getFile() == file) {
+ return doc;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void documentFrameActivated(DocumentFrame documentFrame) {
+ upperDocumentFrame = documentFrame;
+ }
+
+ // Close the window when the close box is clicked
+ private void documentFrameClosing(DocumentFrame documentFrame) {
+ if (documentFrame.requestClose()) {
+ closeDocumentFrame(documentFrame);
+ }
+ }
+
+ // Close the documentFrame without further discussion
+ private void closeDocumentFrame(DocumentFrame documentFrame) {
+ documents.remove(documentFrame);
+ if (documentFrame.getWindowListeners().length > 0) {
+ documentFrame.removeWindowListener(documentFrame.getWindowListeners()[0]);
+ }
+ documentFrame.setVisible(false);
+ documentFrame.dispose();
+ }
+
+ private void addDocumentFrame(DocumentFrame documentFrame) {
+ documentFrame.initialize();
+ documentFrame.setVisible(true);
+
+ // event handling
+ documentFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+ public void windowActivated(java.awt.event.WindowEvent e) {
+ documentFrameActivated((DocumentFrame) e.getWindow());
+ }
+
+ public void windowClosing(java.awt.event.WindowEvent e) {
+ documentFrameClosing((DocumentFrame) e.getWindow());
+ }
+ });
+
+ documents.add(documentFrame);
+ upperDocumentFrame = documentFrame;
+ }
+
+ private void setupFramelessMenuBar() {
+ if (Utils.isMacOSX() &&
+ System.getProperty("apple.laf.useScreenMenuBar").equalsIgnoreCase("true")) {
+ if (invisibleFrame == null) {
+ // We use reflection here because the setUndecorated() method
+ // only exists in Java 1.4 and up
+ invisibleFrame = new AbstractFrame() {
+
+ protected void initializeComponents() {
+ getSaveAction().setEnabled(false);
+ getSaveAsAction().setEnabled(false);
+ if (getImportAction() != null) getImportAction().setEnabled(false);
+ if (getExportAction() != null) getExportAction().setEnabled(false);
+ getPrintAction().setEnabled(false);
+
+ getCutAction().setEnabled(false);
+ getCopyAction().setEnabled(false);
+ getPasteAction().setEnabled(false);
+ getDeleteAction().setEnabled(false);
+ getSelectAllAction().setEnabled(false);
+ getFindAction().setEnabled(false);
+
+ getZoomWindowAction().setEnabled(false);
+ getMinimizeWindowAction().setEnabled(false);
+ getCloseWindowAction().setEnabled(false);
+
+ }
+
+ public boolean requestClose() {
+ return false;
+ }
+
+ public JComponent getExportableComponent() {
+ return null;
+ }
+ };
+ invisibleFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ try {
+ Method mthd = invisibleFrame.getClass().getMethod("setUndecorated",
+ new Class[]{Boolean.TYPE});
+ mthd.invoke(invisibleFrame, new Object[]{Boolean.TRUE});
+ } catch (Exception ex) {
+ // Shouldn't happen
+ }
+ invisibleFrame.setSize(0, 0);
+ invisibleFrame.pack();
+ }
+ invisibleFrame.initialize();
+
+ if (!invisibleFrame.isVisible())
+ invisibleFrame.setVisible(true);
+
+ invisibleFrame.pack();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/MultiDocMenuBarFactory.java b/src/org/virion/jam/framework/MultiDocMenuBarFactory.java
new file mode 100644
index 0000000..5f64986
--- /dev/null
+++ b/src/org/virion/jam/framework/MultiDocMenuBarFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2005 Biomatters LTD. All Rights Reserved.
+ */
+
+package org.virion.jam.framework;
+
+import org.virion.jam.mac.MacFileMenuFactory;
+import org.virion.jam.mac.MacHelpMenuFactory;
+import org.virion.jam.mac.MacWindowMenuFactory;
+
+
+public class MultiDocMenuBarFactory extends DefaultMenuBarFactory {
+
+
+ public MultiDocMenuBarFactory() {
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ registerMenuFactory(new MacFileMenuFactory(true));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new MacHelpMenuFactory());
+ registerMenuFactory(new MacWindowMenuFactory());
+ } else {
+ registerMenuFactory(new DefaultFileMenuFactory(true));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new DefaultHelpMenuFactory());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/RecentFileList.java b/src/org/virion/jam/framework/RecentFileList.java
new file mode 100644
index 0000000..aefe695
--- /dev/null
+++ b/src/org/virion/jam/framework/RecentFileList.java
@@ -0,0 +1,196 @@
+/**
+ * RecentFileList.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import javax.swing.event.EventListenerList;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * A class for maintaining a "Recent File List".
+ * The recent file list can be stored between program invocations in
+ * a properties file. One or more RecentFileLists can easily
+ * be embedded in a JMenu.
+ *
+ * @author Tony Johnson (tonyj at slac.stanford.edu)
+ * @version $Id: RecentFileList.java 183 2006-01-23 21:29:48Z rambaut $
+ */
+public class RecentFileList implements ActionListener {
+ /**
+ * Create a RecentFileList
+ */
+ public RecentFileList() {
+ this(null);
+ }
+
+ /**
+ * Create a RecentFileList with a given maximum length
+ *
+ * @param size the maximum number of files to remember
+ */
+ public RecentFileList(int size) {
+ this(null, size);
+ }
+
+ /**
+ * Create a recent file list. The type parameter is used to
+ * prefix entries in the properties file, so that multiple
+ * RecentFileLists can be used in an application.
+ *
+ * @param type The prefix to use
+ */
+ public RecentFileList(String type) {
+ this(type, 4);
+ }
+
+ /**
+ * Create a recent file list with a given type and size
+ *
+ * @param type The prefix to use
+ * @param size the maximum number of files to remember
+ */
+ public RecentFileList(String type, int size) {
+ files = new Vector(size);
+ this.size = size;
+ this.type = type;
+ used = 0;
+ }
+
+ public void addActionListener(ActionListener l) {
+ listenerList.add(ActionListener.class, l);
+ }
+
+ public void removeActionListener(ActionListener l) {
+ listenerList.remove(ActionListener.class, l);
+ }
+
+ /**
+ * An action event is fired when the user selects one of the
+ * files from a menu. The "actionCommand" in the event will be
+ * set to the name of the selected file.
+ */
+ protected void fireActionPerformed(ActionEvent e) {
+ // 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] == ActionListener.class) {
+ ((ActionListener) listeners[i + 1]).actionPerformed(e);
+ }
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ fireActionPerformed(e);
+ }
+
+ /**
+ * Save the recent file list in a Properties set
+ *
+ * @param props The Properties set to save the files in
+ */
+ public void save(Properties props) {
+ String key = "RecentFile_" + (type != null ? type + "_" : "");
+ for (int i = 0; i < used; i++) {
+ props.put(key + i, files.elementAt(i).toString());
+ }
+ props.put(key + "Used", String.valueOf(used));
+ }
+
+ /**
+ * Load the recent file list from a Properties set
+ *
+ * @param props The Properties set to load from
+ */
+ public void load(Properties props) {
+ String key = "RecentFile_" + (type != null ? type + "_" : "");
+ files.removeAllElements();
+ used = Integer.parseInt(props.getProperty(key + "Used", "0"));
+ for (int i = 0; i < used; i++) {
+ String value = props.getProperty(key + i);
+ if (value == null) break;
+ files.addElement(value);
+ }
+ }
+
+ /**
+ * Add a file to the list
+ *
+ * @param f The file to add
+ */
+ public void add(File f) {
+ try {
+ add(f.getCanonicalPath());
+ } catch (java.io.IOException x) {
+ add(f.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Remove a file from the list
+ *
+ * @param f Remove a file from the list
+ */
+ public void remove(File f) {
+ remove(f.getName());
+ }
+
+ /**
+ * Add a file to the list
+ *
+ * @param name The name of the file to add
+ */
+ public void add(String name) {
+ int pos = files.indexOf(name);
+ if (pos > 0) {
+ files.removeElementAt(pos);
+ files.insertElementAt(name, 0);
+ } else if (pos != 0) {
+ if (used == size)
+ files.removeElementAt(size - 1);
+ else
+ used++;
+ files.insertElementAt(name, 0);
+ }
+ }
+
+ /**
+ * Remove a file from the list
+ *
+ * @param name The name of the file to remove
+ */
+ public void remove(String name) {
+ if (files.removeElement(name)) used--;
+ }
+
+ /**
+ * Adds the recent file list to a menu.
+ * The files will be added at the end of the menu, with a
+ * separator before the files (if there are >0 files in the list)
+ */
+ public void buildMenu(JMenu menu) {
+ if (used > 0) {
+ menu.addSeparator();
+ for (int i = 0; i < used; i++) {
+ JMenuItem item = new JMenuItem(String.valueOf(i + 1) + " " + files.elementAt(i));
+ item.setActionCommand(files.elementAt(i).toString());
+ if (size < 9) item.setMnemonic(Character.forDigit(i + 1, 10));
+ item.addActionListener(this);
+ menu.add(item);
+ }
+ }
+ }
+
+ private Vector files;
+ private String type;
+ private int size;
+ private int used;
+ private EventListenerList listenerList = new EventListenerList();
+}
diff --git a/src/org/virion/jam/framework/SingleDocApplication.java b/src/org/virion/jam/framework/SingleDocApplication.java
new file mode 100644
index 0000000..9bbbc65
--- /dev/null
+++ b/src/org/virion/jam/framework/SingleDocApplication.java
@@ -0,0 +1,93 @@
+/**
+ * SingleDocApplication.java
+ */
+
+package org.virion.jam.framework;
+
+import javax.swing.*;
+import java.io.File;
+
+public class SingleDocApplication extends Application {
+
+ private DocumentFrame documentFrame = null;
+
+ public SingleDocApplication(String nameString, String aboutString, Icon icon) {
+
+ super(new SingleDocMenuBarFactory(), nameString, aboutString, icon);
+ }
+
+ public SingleDocApplication(String nameString, String aboutString, Icon icon,
+ String websiteURLString, String helpURLString) {
+
+ super(new SingleDocMenuBarFactory(), nameString, aboutString, icon, websiteURLString, helpURLString);
+ }
+
+ public SingleDocApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon) {
+
+ super(menuBarFactory, nameString, aboutString, icon);
+ }
+
+ public SingleDocApplication(MenuBarFactory menuBarFactory, String nameString, String aboutString, Icon icon,
+ String websiteURLString, String helpURLString) {
+
+ super(menuBarFactory, nameString, aboutString, icon, websiteURLString, helpURLString);
+ }
+
+ public final void initialize() {
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ // If this is a Mac application then register it at this point.
+ // This will result in any events such as open file being executed
+ // due to files being double-clicked or dragged on to the application.
+ org.virion.jam.mac.Utils.macOSXRegistration(this);
+ }
+ }
+
+ public void setDocumentFrame(DocumentFrame documentFrame) {
+
+ this.documentFrame = documentFrame;
+
+ documentFrame.initialize();
+ documentFrame.setVisible(true);
+
+ // event handling
+ documentFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+ public void windowClosing(java.awt.event.WindowEvent e) {
+ thisWindowClosing(e);
+ }
+ });
+ }
+
+ protected JFrame getDefaultFrame() { return documentFrame; }
+
+ protected String getDocumentExtension() { return ""; }
+
+ public DocumentFrame doNew() {
+ throw new RuntimeException("A SingleDocApplication cannot do a New command");
+ }
+
+ public DocumentFrame doOpenFile(File file) {
+ documentFrame.openFile(file);
+ return documentFrame;
+ }
+
+ public void doCloseWindow() {
+ doQuit();
+ }
+
+ public void doQuit() {
+ if (documentFrame == null) {
+ return;
+ }
+ if (documentFrame.requestClose()) {
+
+ documentFrame.setVisible(false);
+ documentFrame.dispose();
+ System.exit(0);
+ }
+ }
+
+ // Close the window when the close box is clicked
+ private void thisWindowClosing(java.awt.event.WindowEvent e) {
+ doQuit();
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/framework/SingleDocMenuBarFactory.java b/src/org/virion/jam/framework/SingleDocMenuBarFactory.java
new file mode 100644
index 0000000..1af4fe1
--- /dev/null
+++ b/src/org/virion/jam/framework/SingleDocMenuBarFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2005 Biomatters LTD. All Rights Reserved.
+ */
+
+package org.virion.jam.framework;
+
+import org.virion.jam.mac.MacFileMenuFactory;
+import org.virion.jam.mac.MacHelpMenuFactory;
+import org.virion.jam.mac.MacWindowMenuFactory;
+
+
+public class SingleDocMenuBarFactory extends DefaultMenuBarFactory {
+
+ public SingleDocMenuBarFactory() {
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ registerMenuFactory(new MacFileMenuFactory(false));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new MacWindowMenuFactory());
+ registerMenuFactory(new MacHelpMenuFactory());
+ } else {
+ registerMenuFactory(new DefaultFileMenuFactory(false));
+ registerMenuFactory(new DefaultEditMenuFactory());
+ registerMenuFactory(new DefaultHelpMenuFactory());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/html/HTMLViewer.java b/src/org/virion/jam/html/HTMLViewer.java
new file mode 100644
index 0000000..dba3ad5
--- /dev/null
+++ b/src/org/virion/jam/html/HTMLViewer.java
@@ -0,0 +1,25 @@
+package org.virion.jam.html;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * General-purpose class to display HTML in a standalone frame.
+ */
+public class HTMLViewer extends JFrame {
+
+ private JEditorPane editorPane;
+
+ public HTMLViewer(String title, String html) {
+ super(title);
+ getContentPane().setLayout(new BorderLayout());
+ editorPane = new JEditorPane("text/html", html);
+ editorPane.setEditable(false);
+ getContentPane().add(new JScrollPane(editorPane), BorderLayout.CENTER);
+ }
+}
+
+
+
+
+
diff --git a/src/org/virion/jam/html/SimpleLinkListener.java b/src/org/virion/jam/html/SimpleLinkListener.java
new file mode 100644
index 0000000..1aa4b7f
--- /dev/null
+++ b/src/org/virion/jam/html/SimpleLinkListener.java
@@ -0,0 +1,33 @@
+package org.virion.jam.html;
+
+
+import javax.swing.event.*;
+
+import org.virion.jam.util.BrowserLauncher;
+
+/**
+ * iSeek prototype. Codename seekquence.
+ *
+ * This class listens to Hyperlink Events, and opens the url in a browser window.
+ *
+ * Open a browser from a Java application on Windows, Unix, or Macintosh.
+ * see http://ostermiller.org/utils/Browser.html for more information
+ *
+ * @author Nasser
+ * @version $Id: SimpleLinkListener.java 183 2006-01-23 21:29:48Z rambaut $
+ * Date: 26/01/2005
+ * Time: 11:54:50
+ */
+public class SimpleLinkListener implements HyperlinkListener {
+
+ public void hyperlinkUpdate(HyperlinkEvent he) {
+
+ if (he.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ try{
+ BrowserLauncher.openURL(he.getDescription());
+ }catch(Exception ioe){
+ ioe.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/org/virion/jam/layouts/CompassLayout.java b/src/org/virion/jam/layouts/CompassLayout.java
new file mode 100644
index 0000000..13f7d4d
--- /dev/null
+++ b/src/org/virion/jam/layouts/CompassLayout.java
@@ -0,0 +1,363 @@
+package org.virion.jam.layouts;
+
+import java.awt.*;
+
+/**
+ * A layout manager similar to BorderLayout but with 8 compass directions. It will
+ * layout a container using members named "North", "NorthEast", "East", etc. and
+ * "Center".
+ * <p/>
+ * The "North", "South", "East" and "West" components get layed out
+ * according to their preferred sizes and the constraints of the
+ * container's size. The "Center" component will get any space left
+ * over. The corner components are layed out accordingly
+ *
+ * @author Andrew Rambaut
+ */
+
+public class CompassLayout implements LayoutManager2 {
+ int hgap;
+ int vgap;
+
+ Component north, northWest;
+ Component west, southWest;
+ Component east, northEast;
+ Component south, southEast;
+ Component center;
+
+ /**
+ * Constructs a new CompassLayout.
+ */
+ public CompassLayout() {
+ this(0, 0);
+ }
+
+ /**
+ * Constructs a CompassLayout with the specified gaps.
+ *
+ * @param hgap the horizontal gap
+ * @param vgap the vertical gap
+ */
+ public CompassLayout(int hgap, int vgap) {
+ this.hgap = hgap;
+ this.vgap = vgap;
+ }
+
+ /**
+ * Returns the horizontal gap between components.
+ */
+ public int getHgap() {
+ return hgap;
+ }
+
+ /**
+ * Sets the horizontal gap between components.
+ *
+ * @param hgap the horizontal gap between components
+ */
+ public void setHgap(int hgap) {
+ this.hgap = hgap;
+ }
+
+ /**
+ * Returns the vertical gap between components.
+ */
+ public int getVgap() {
+ return vgap;
+ }
+
+ /**
+ * Sets the vertical gap between components.
+ *
+ * @param vgap the vertical gap between components
+ */
+ public void setVgap(int vgap) {
+ this.vgap = vgap;
+ }
+
+ /**
+ * Adds the specified named component to the layout.
+ */
+ public void addLayoutComponent(Component comp, Object constraints) {
+ synchronized (comp.getTreeLock()) {
+ if ((constraints == null) || (constraints instanceof String)) {
+ addLayoutComponent((String) constraints, comp);
+ } else {
+ throw new IllegalArgumentException("cannot add to layout: constraint must be a string (or null)");
+ }
+ }
+ }
+
+ /**
+ * @deprecated replaced by <code>addLayoutComponent(Component, Object)</code>.
+ */
+ public void addLayoutComponent(String name, Component comp) {
+ synchronized (comp.getTreeLock()) {
+ /* Special case: treat null the same as "Center". */
+ if (name == null) {
+ name = "Center";
+ }
+
+ if ("Center".equals(name)) {
+ center = comp;
+ } else if ("North".equals(name)) {
+ north = comp;
+ } else if ("South".equals(name)) {
+ south = comp;
+ } else if ("East".equals(name)) {
+ east = comp;
+ } else if ("West".equals(name)) {
+ west = comp;
+ } else if ("NorthEast".equals(name)) {
+ northEast = comp;
+ } else if ("SouthEast".equals(name)) {
+ southEast = comp;
+ } else if ("NorthWest".equals(name)) {
+ northWest = comp;
+ } else if ("SouthWest".equals(name)) {
+ southWest = comp;
+ } else {
+ throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified component from the layout.
+ *
+ * @param comp the component to be removed
+ */
+ public void removeLayoutComponent(Component comp) {
+ synchronized (comp.getTreeLock()) {
+ if (comp == center) {
+ center = null;
+ } else if (comp == north) {
+ north = null;
+ } else if (comp == south) {
+ south = null;
+ } else if (comp == east) {
+ east = null;
+ } else if (comp == west) {
+ west = null;
+ } else if (comp == northEast) {
+ northEast = null;
+ } else if (comp == southEast) {
+ southEast = null;
+ } else if (comp == northWest) {
+ northWest = null;
+ } else if (comp == southWest) {
+ southWest = null;
+ }
+ }
+ }
+
+ /**
+ * Returns the minimum dimensions needed to layout the components
+ * contained in the specified target container.
+ *
+ * @param target the Container on which to do the layout
+ * @see Container
+ * @see #preferredLayoutSize
+ */
+ public Dimension minimumLayoutSize(Container target) {
+ synchronized (target.getTreeLock()) {
+ Dimension dim = new Dimension(0, 0);
+
+ if ((east != null) && east.isVisible()) {
+ Dimension d = east.getMinimumSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((west != null) && west.isVisible()) {
+ Dimension d = west.getMinimumSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((center != null) && center.isVisible()) {
+ Dimension d = center.getMinimumSize();
+ dim.width += d.width;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((north != null) && north.isVisible()) {
+ Dimension d = north.getMinimumSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+ if ((south != null) && south.isVisible()) {
+ Dimension d = south.getMinimumSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+
+ Insets insets = target.getInsets();
+ dim.width += insets.left + insets.right;
+ dim.height += insets.top + insets.bottom;
+
+ return dim;
+ }
+ }
+
+ /**
+ * Returns the preferred dimensions for this layout given the components
+ * in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ * @see Container
+ * @see #minimumLayoutSize
+ */
+ public Dimension preferredLayoutSize(Container target) {
+ synchronized (target.getTreeLock()) {
+ Dimension dim = new Dimension(0, 0);
+
+ if ((east != null) && east.isVisible()) {
+ Dimension d = east.getPreferredSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((west != null) && west.isVisible()) {
+ Dimension d = west.getPreferredSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((center != null) && center.isVisible()) {
+ Dimension d = center.getPreferredSize();
+ dim.width += d.width;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((north != null) && north.isVisible()) {
+ Dimension d = north.getPreferredSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+ if ((south != null) && south.isVisible()) {
+ Dimension d = south.getPreferredSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+
+ Insets insets = target.getInsets();
+ dim.width += insets.left + insets.right;
+ dim.height += insets.top + insets.bottom;
+
+ return dim;
+ }
+ }
+
+ /**
+ * Returns the maximum dimensions for this layout given the components
+ * in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ */
+ public Dimension maximumLayoutSize(Container target) {
+ return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the alignment along the x axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ */
+ public float getLayoutAlignmentX(Container parent) {
+ return 0.5f;
+ }
+
+ /**
+ * Returns the alignment along the y axis. This specifies how
+ * the component would like to be aligned relative to other
+ * components. The value should be a number between 0 and 1
+ * where 0 represents alignment along the origin, 1 is aligned
+ * the furthest away from the origin, 0.5 is centered, etc.
+ */
+ public float getLayoutAlignmentY(Container parent) {
+ return 0.5f;
+ }
+
+ /**
+ * Invalidates the layout, indicating that if the layout manager
+ * has cached information it should be discarded.
+ */
+ public void invalidateLayout(Container target) {
+ }
+
+ /**
+ * Lays out the specified container. This method will actually reshape the
+ * components in the specified target container in order to satisfy the
+ * constraints of the CompassLayout object.
+ *
+ * @param target the component being laid out
+ * @see Container
+ */
+ public void layoutContainer(Container target) {
+ synchronized (target.getTreeLock()) {
+ Insets insets = target.getInsets();
+ Dimension d, dn, ds, de, dw;
+
+ d = target.getSize();
+
+ int top = insets.top;
+ int bottom = d.height - insets.bottom;
+ int left = insets.left;
+ int right = d.width - insets.right;
+
+ // defines the centre box.
+ int top1 = top;
+ int bottom1 = bottom;
+ int left1 = left;
+ int right1 = right;
+
+ if ((north != null) && north.isVisible()) {
+ d = north.getPreferredSize();
+ top1 += d.height + vgap;
+ }
+ if ((south != null) && south.isVisible()) {
+ d = south.getPreferredSize();
+ bottom1 -= d.height + vgap;
+ }
+ if ((east != null) && east.isVisible()) {
+ d = east.getPreferredSize();
+ right1 -= d.width + hgap;
+ }
+ if ((west != null) && west.isVisible()) {
+ d = west.getPreferredSize();
+ left1 += d.width + hgap;
+ }
+
+ if ((north != null) && north.isVisible())
+ north.setBounds(left1, top, right1 - left1, top1 - top);
+
+ if ((south != null) && south.isVisible())
+ south.setBounds(left1, bottom1, right1 - left1, bottom - bottom1);
+
+ if ((east != null) && east.isVisible())
+ east.setBounds(right1, top1, right - right1, bottom1 - top1);
+
+ if ((west != null) && west.isVisible())
+ west.setBounds(left, top, left1 - left, bottom - top);
+
+ if ((center != null) && center.isVisible())
+ center.setBounds(left1, top1, right1 - left1, bottom1 - top1);
+
+ if ((northWest != null) && northWest.isVisible())
+ northWest.setBounds(left, top, left1 - left, top1 - top);
+
+ if ((southWest != null) && southWest.isVisible())
+ southWest.setBounds(left, bottom1, left1 - left, bottom - bottom1);
+
+ if ((northEast != null) && northEast.isVisible())
+ northEast.setBounds(right1, top, right - right1, top1 - top);
+
+ if ((southEast != null) && southEast.isVisible())
+ southEast.setBounds(right1, bottom1, right - right1, bottom - bottom1);
+ }
+ }
+
+ /**
+ * Returns the String representation of this CompassLayout's values.
+ */
+ public String toString() {
+ return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
+ }
+}
diff --git a/src/org/virion/jam/mac/MacFileMenuFactory.java b/src/org/virion/jam/mac/MacFileMenuFactory.java
new file mode 100644
index 0000000..860bfc7
--- /dev/null
+++ b/src/org/virion/jam/mac/MacFileMenuFactory.java
@@ -0,0 +1,98 @@
+package org.virion.jam.mac;
+
+import org.virion.jam.framework.MenuFactory;
+import org.virion.jam.framework.AbstractFrame;
+import org.virion.jam.framework.Application;
+import org.virion.jam.framework.MenuBarFactory;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.ActionEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:02:45 AM
+ */
+public class MacFileMenuFactory implements MenuFactory {
+
+ private final boolean isMultiDocument;
+
+ public MacFileMenuFactory(boolean isMultiDocument) {
+ this.isMultiDocument = isMultiDocument;
+ }
+
+ public String getMenuName() {
+ return "File";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ Application application = Application.getApplication();
+ JMenuItem item;
+
+ if (isMultiDocument) {
+ item = new JMenuItem(application.getNewAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+
+ item = new JMenuItem(application.getOpenAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ if (application.getRecentFileMenu() != null) {
+ JMenu subMenu = application.getRecentFileMenu();
+ menu.add(subMenu);
+ }
+
+ menu.addSeparator();
+
+ item = new JMenuItem(frame.getCloseWindowAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getSaveAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(frame.getSaveAsAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MenuBarFactory.MENU_MASK + ActionEvent.SHIFT_MASK));
+ menu.add(item);
+
+ item = new JMenuItem("Revert to Saved");
+ item.setEnabled(false);
+ menu.add(item);
+
+ if (frame.getImportAction() != null || frame.getExportAction() != null) {
+ menu.addSeparator();
+
+ if (frame.getImportAction() != null) {
+ item = new JMenuItem(frame.getImportAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+
+ if (frame.getExportAction() != null) {
+ item = new JMenuItem(frame.getExportAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+ }
+ }
+
+ menu.addSeparator();
+
+ item = new JMenuItem(frame.getPrintAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ item = new JMenuItem(application.getPageSetupAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, MenuBarFactory.MENU_MASK + ActionEvent.SHIFT_MASK));
+ menu.add(item);
+
+ }
+
+ public int getPreferredAlignment() {
+ return LEFT;
+ }
+}
diff --git a/src/org/virion/jam/mac/MacHelpMenuFactory.java b/src/org/virion/jam/mac/MacHelpMenuFactory.java
new file mode 100644
index 0000000..579a5eb
--- /dev/null
+++ b/src/org/virion/jam/mac/MacHelpMenuFactory.java
@@ -0,0 +1,51 @@
+package org.virion.jam.mac;
+
+import org.virion.jam.framework.MenuFactory;
+import org.virion.jam.framework.AbstractFrame;
+import org.virion.jam.framework.Application;
+import org.virion.jam.framework.MenuBarFactory;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:04:02 AM
+ */
+public class MacHelpMenuFactory implements MenuFactory {
+ public String getMenuName() {
+ return "Help";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ JMenuItem item;
+
+ Application application = Application.getApplication();
+
+ if (frame.getHelpAction() != null) {
+ item = new JMenuItem(frame.getHelpAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ menu.addSeparator();
+ }
+
+ if (application.getHelpAction() != null) {
+ item = new JMenuItem(application.getHelpAction());
+ menu.add(item);
+
+ menu.addSeparator();
+ }
+
+ if (application.getWebsiteAction() != null) {
+ item = new JMenuItem("Website");
+ }
+ }
+
+ public int getPreferredAlignment() {
+ return RIGHT;
+ }
+
+}
diff --git a/src/org/virion/jam/mac/MacWindowMenuFactory.java b/src/org/virion/jam/mac/MacWindowMenuFactory.java
new file mode 100644
index 0000000..57616cb
--- /dev/null
+++ b/src/org/virion/jam/mac/MacWindowMenuFactory.java
@@ -0,0 +1,39 @@
+package org.virion.jam.mac;
+
+import org.virion.jam.framework.MenuFactory;
+import org.virion.jam.framework.AbstractFrame;
+import org.virion.jam.framework.Application;
+import org.virion.jam.framework.MenuBarFactory;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author rambaut
+ * Date: Dec 26, 2004
+ * Time: 11:03:39 AM
+ */
+public class MacWindowMenuFactory implements MenuFactory {
+ public String getMenuName() {
+ return "Window";
+ }
+
+ public void populateMenu(JMenu menu, AbstractFrame frame) {
+
+ Application application = Application.getApplication();
+
+ JMenuItem item;
+
+ item = new JMenuItem(frame.getZoomWindowAction());
+ menu.add(item);
+
+ item = new JMenuItem(frame.getMinimizeWindowAction());
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, MenuBarFactory.MENU_MASK));
+ menu.add(item);
+
+ }
+
+ public int getPreferredAlignment() {
+ return RIGHT;
+ }
+}
diff --git a/src/org/virion/jam/mac/Utils.java b/src/org/virion/jam/mac/Utils.java
new file mode 100644
index 0000000..673782b
--- /dev/null
+++ b/src/org/virion/jam/mac/Utils.java
@@ -0,0 +1,60 @@
+/* Utils.java */
+
+package org.virion.jam.mac;
+
+import java.lang.reflect.Method;
+
+public class Utils {
+
+ protected static boolean MAC_OS_X;
+ protected static String MAC_OS_X_VERSION;
+
+ public static boolean isMacOSX() {
+ return MAC_OS_X;
+ }
+
+ public static void macOSXRegistration(org.virion.jam.framework.Application application) {
+ if (MAC_OS_X) {
+
+ Class osxAdapter = null;
+
+ try {
+ osxAdapter = Class.forName("org.virion.jam.maconly.OSXAdapter");
+ } catch (Exception e) {
+ System.err.println("This version of Mac OS X does not support the Apple EAWT.");
+ }
+
+ try {
+ if (osxAdapter != null) {
+
+ Class[] defArgs = {org.virion.jam.framework.Application.class};
+ Method registerMethod = osxAdapter.getDeclaredMethod("registerMacOSXApplication", defArgs);
+
+ if (registerMethod != null) {
+ Object[] args = {application};
+ registerMethod.invoke(osxAdapter, args);
+ }
+
+ // This is slightly gross. to reflectively access methods with boolean args,
+ // use "boolean.class", then pass a Boolean object in as the arg, which apparently
+ // gets converted for you by the reflection system.
+ defArgs[0] = boolean.class;
+ Method prefsEnableMethod = osxAdapter.getDeclaredMethod("enablePrefs", defArgs);
+ if (prefsEnableMethod != null) {
+ Object args[] = {Boolean.TRUE};
+ prefsEnableMethod.invoke(osxAdapter, args);
+ }
+ }
+
+ } catch (Exception e) {
+ System.err.println("Exception while loading the OSXAdapter:");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ static {
+ MAC_OS_X_VERSION = System.getProperty("mrj.version");
+ MAC_OS_X = MAC_OS_X_VERSION != null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/maconly/OSXAdapter.java b/src/org/virion/jam/maconly/OSXAdapter.java
new file mode 100644
index 0000000..071c947
--- /dev/null
+++ b/src/org/virion/jam/maconly/OSXAdapter.java
@@ -0,0 +1,85 @@
+/* OSXAdapter.java
+
+package org.virion.jam.maconly;
+
+import com.apple.eawt.ApplicationAdapter;
+import com.apple.eawt.ApplicationEvent;
+
+public class OSXAdapter extends ApplicationAdapter {
+
+ // pseudo-singleton model; no point in making multiple instances
+ // of the EAWT application or our adapter
+ private static OSXAdapter theAdapter;
+ private static com.apple.eawt.Application theApplication;
+
+ // reference to the app where the existing quit, about, prefs code is
+ private org.virion.jam.framework.Application application;
+
+ private OSXAdapter(org.virion.jam.framework.Application application) {
+ this.application = application;
+ }
+
+ // implemented handler methods. These are basically hooks into existing
+ // functionality from the main app, as if it came over from another platform.
+ public void handleAbout(ApplicationEvent ae) {
+ if (application != null) {
+ ae.setHandled(true);
+ application.doAbout();
+ } else {
+ throw new IllegalStateException("handleAbout: Application instance detached from listener");
+ }
+ }
+
+ public void handlePreferences(ApplicationEvent ae) {
+ if (application != null) {
+ application.doPreferences();
+ ae.setHandled(true);
+ } else {
+ throw new IllegalStateException("handlePreferences: Application instance detached from listener");
+ }
+ }
+
+ public void handleQuit(ApplicationEvent ae) {
+ if (application != null) {
+
+ ae.setHandled(false);
+ application.doQuit();
+ } else {
+ throw new IllegalStateException("handleQuit: Application instance detached from listener");
+ }
+ }
+
+
+ // The main entry-point for this functionality. This is the only method
+ // that needs to be called at runtime, and it can easily be done using
+ // reflection.
+ public static void registerMacOSXApplication(org.virion.jam.framework.Application application) {
+ if (theApplication == null) {
+ theApplication = new com.apple.eawt.Application();
+ }
+
+ if (theAdapter == null) {
+ theAdapter = new OSXAdapter(application);
+ }
+ theApplication.addApplicationListener(theAdapter);
+ }
+
+ // Another static entry point for EAWT functionality. Enables the
+ // "Preferences..." menu item in the application menu.
+ public static void enablePrefs(boolean enabled) {
+ if (theApplication == null) {
+ theApplication = new com.apple.eawt.Application();
+ }
+ theApplication.setEnabledPreferencesMenu(enabled);
+ }
+
+ public void handleOpenFile(ApplicationEvent ae) {
+ if (application != null) {
+ application.doOpen(ae.getFilename());
+ ae.setHandled(true);
+ } else {
+ throw new IllegalStateException("handleOpenFile: Application instance detached from listener");
+ }
+ throw new RuntimeException("handleOpenFile: " + ae.getFilename());
+ }
+}*/
\ No newline at end of file
diff --git a/src/org/virion/jam/panels/ActionPanel.java b/src/org/virion/jam/panels/ActionPanel.java
new file mode 100644
index 0000000..61cbf53
--- /dev/null
+++ b/src/org/virion/jam/panels/ActionPanel.java
@@ -0,0 +1,121 @@
+package org.virion.jam.panels;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Creates a panel consisting of three buttons: an add button, a remove button
+ * and an action button. At present these will look similar to buttons found in
+ * Apple Mac OS X applications such as Mail. In future this class could be given
+ * appropriate LAF classes to render appropriate to the platform.
+ * @author rambaut
+ * Date: Jul 29, 2004
+ * Time: 9:38:58 AM
+ */
+public class ActionPanel extends JPanel {
+ private JButton addButton;
+ private JButton removeButton;
+ private JButton actionButton;
+
+ private Icon addIcon;
+ private Icon removeIcon;
+ private Icon actionIcon;
+
+ public ActionPanel() {
+ this(true);
+ }
+
+ public ActionPanel(boolean useActionButton) {
+ setLayout(new FlowLayout(java.awt.FlowLayout.LEFT,0,0));
+ setOpaque(false);
+
+ addButton = new JButton("+");
+ addButton.putClientProperty("JButton.buttonType", "toolbar");
+
+ addIcon = IconUtils.getIcon(ActionPanel.class, "images/add/addButton.png");
+ if (addIcon != null) {
+ addButton.setIcon(addIcon);
+ addButton.setPressedIcon(IconUtils.getIcon(ActionPanel.class, "images/add/addButtonPressed.png"));
+ addButton.setDisabledIcon(IconUtils.getIcon(ActionPanel.class, "images/add/addButtonInactive.png"));
+ addButton.setText(null);
+ addButton.setPreferredSize(new Dimension(addIcon.getIconWidth(), addIcon.getIconHeight()));
+ }
+ addButton.setBorderPainted(false);
+ addButton.setOpaque(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ addButton.setContentAreaFilled(false);
+
+ removeButton = new JButton("-");
+ removeButton.putClientProperty("JButton.buttonType", "toolbar");
+
+ removeIcon = IconUtils.getIcon(ActionPanel.class, "images/remove/removeButton.png");
+ if (removeIcon != null) {
+ removeButton.setIcon(removeIcon);
+ removeButton.setPressedIcon(IconUtils.getIcon(ActionPanel.class, "images/remove/removeButtonPressed.png"));
+ removeButton.setDisabledIcon(IconUtils.getIcon(ActionPanel.class, "images/remove/removeButtonInactive.png"));
+ removeButton.setText(null);
+ removeButton.setPreferredSize(new Dimension(removeIcon.getIconWidth(), removeIcon.getIconHeight()));
+ }
+ removeButton.setBorderPainted(false);
+ removeButton.setOpaque(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ removeButton.setContentAreaFilled(false);
+
+ add(addButton);
+ add(removeButton);
+
+ if (useActionButton) {
+ actionButton = new JButton("*");
+ actionButton.putClientProperty("JButton.buttonType", "toolbar");
+
+ actionIcon = IconUtils.getIcon(ActionPanel.class, "images/action/actionButton.png");
+ if (actionIcon != null) {
+ actionButton.setIcon(actionIcon);
+ actionButton.setPressedIcon(IconUtils.getIcon(ActionPanel.class, "images/action/actionButtonPressed.png"));
+ actionButton.setDisabledIcon(IconUtils.getIcon(ActionPanel.class, "images/action/actionButtonInactive.png"));
+ actionButton.setText(null);
+ actionButton.setPreferredSize(new Dimension(actionIcon.getIconWidth(), actionIcon.getIconHeight()));
+ }
+ actionButton.setBorderPainted(false);
+ actionButton.setOpaque(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ actionButton.setContentAreaFilled(false);
+
+ add(new JToolBar.Separator(new Dimension(6,6)));
+ add(actionButton);
+ }
+ }
+
+ public void setAddAction(Action action) {
+ addButton.setAction(action);
+ addButton.setIcon(addIcon);
+ addButton.setText(null);
+ }
+
+ public void setAddToolTipText(String text) {
+ addButton.setToolTipText(text);
+ }
+
+ public void setRemoveAction(Action action) {
+ removeButton.setAction(action);
+ removeButton.setIcon(removeIcon);
+ removeButton.setText(null);
+ }
+
+ public void setRemoveToolTipText(String text) {
+ removeButton.setToolTipText(text);
+ }
+
+ public void setActionAction(Action action) {
+ actionButton.setAction(action);
+ actionButton.setIcon(actionIcon);
+ actionButton.setText(null);
+ }
+
+ public void setActionToolTipText(String text) {
+ actionButton.setToolTipText(text);
+ }
+
+}
diff --git a/src/org/virion/jam/panels/AddRemovePanel.java b/src/org/virion/jam/panels/AddRemovePanel.java
new file mode 100644
index 0000000..241297f
--- /dev/null
+++ b/src/org/virion/jam/panels/AddRemovePanel.java
@@ -0,0 +1,180 @@
+/**
+ * RulesPanel.java
+ */
+
+package org.virion.jam.panels;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.*;
+import java.util.List;
+
+/**
+ * OptionsPanel.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: AddRemovePanel.java 182 2006-01-23 21:24:01Z rambaut $
+ */
+
+
+public abstract class AddRemovePanel extends JPanel {
+
+ private Icon addIcon = null;
+ private Icon addRolloverIcon = null;
+ private Icon addPressedIcon = null;
+ private Icon removeIcon = null;
+ private Icon removeRolloverIcon = null;
+ private Icon removePressedIcon = null;
+
+ public AddRemovePanel() {
+ this(null);
+ }
+
+ public AddRemovePanel(JPanel[] panels) {
+
+ try {
+ addIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/plus.png");
+ addRolloverIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/plusRollover.png");
+ addPressedIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/plusPressed.png");
+ removeIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/minus.png");
+ removeRolloverIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/minusRollover.png");
+ removePressedIcon = IconUtils.getIcon(AddRemovePanel.class, "images/plusminus/minusPressed.png");
+ } catch (Exception e) {
+ // do nothing
+ }
+
+ BoxLayout layout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
+ setLayout(layout);
+ setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+ setOpaque(false);
+
+ if (panels != null && panels.length > 0) {
+ JPanel last = null;
+ for (int i = 0; i < panels.length; i++) {
+ JPanel contents = (JPanel)panels[i];
+ addPanel(last, contents);
+ last = contents;
+ }
+ } else {
+ addPanel(null);
+ }
+
+ Dimension dim = super.getPreferredSize();
+ dim.height += getComponent(0).getPreferredSize().getHeight();
+ setMinimumSize(dim);
+ setPreferredSize(dim);
+ }
+
+ public List getPanels() {
+ return Collections.unmodifiableList(panels);
+ }
+
+ protected abstract JPanel createPanel();
+
+ private void addPanel(JPanel previousPanel) {
+ addPanel(previousPanel, createPanel());
+ }
+
+ private void addPanel(JPanel previousPanel, JPanel contents) {
+
+ RowPanel rowPanel = new RowPanel(contents);
+
+ if (previousPanel != null) {
+ int index = panels.indexOf(previousPanel);
+ add(rowPanel, index + 1);
+ panels.add(index + 1, contents);
+ } else {
+ add(rowPanel, 0);
+ panels.add(0, contents);
+ }
+ removeAction.setEnabled(panels.size() > 1);
+ validate();
+ repaint();
+ }
+
+ private void removePanel(JPanel panel) {
+ int index = panels.indexOf(panel);
+ remove(index);
+ panels.remove(index);
+ removeAction.setEnabled(panels.size() > 1);
+ validate();
+ repaint();
+ }
+
+ class RowPanel extends JPanel {
+ RowPanel(final JPanel contents) {
+
+ setLayout(new GridBagLayout());
+
+ setOpaque(true);
+ setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.lightGray));
+ setBackground(new Color(0.0F, 0.0F, 0.0F, 0.05F));
+
+ JButton addButton = new JButton("+");
+ addButton.putClientProperty("JButton.buttonType", "toolbar");
+// addButton.setBorderPainted(false);
+ addButton.setOpaque(false);
+ if (addIcon != null) {
+ addButton.setIcon(addIcon);
+ addButton.setPressedIcon(addPressedIcon);
+ addButton.setRolloverIcon(addRolloverIcon);
+ addButton.setRolloverEnabled(true);
+ addButton.setText(null);
+ }
+ addButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ addPanel(contents);
+ }
+ });
+ addButton.setEnabled(true);
+
+ JButton removeButton = new JButton(removeAction);
+ removeButton.putClientProperty("JButton.buttonType", "toolbar");
+// removeButton.setBorderPainted(false);
+ removeButton.setOpaque(false);
+ if (removeIcon != null) {
+ removeButton.setIcon(removeIcon);
+ removeButton.setPressedIcon(removePressedIcon);
+ removeButton.setRolloverIcon(removeRolloverIcon);
+ removeButton.setRolloverEnabled(true);
+ removeButton.setText(null);
+ }
+ removeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ removePanel(contents);
+ }
+ });
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.0;
+ c.weighty = 0.0;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = new Insets(1, 2, 0, 2);
+ c.gridx = GridBagConstraints.RELATIVE;
+ c.weightx = 1.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ add(contents, c);
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.0;
+ add(addButton, c);
+ add(removeButton, c);
+ }
+ };
+
+ AbstractAction removeAction = new AbstractAction("-") {
+ public void actionPerformed(ActionEvent ae) {
+ }
+ };
+
+
+ private ArrayList panels = new ArrayList();
+
+
+}
diff --git a/src/org/virion/jam/panels/OptionsPanel.java b/src/org/virion/jam/panels/OptionsPanel.java
new file mode 100644
index 0000000..1ccacf7
--- /dev/null
+++ b/src/org/virion/jam/panels/OptionsPanel.java
@@ -0,0 +1,138 @@
+/**
+ * OptionsPanel.java
+ */
+
+package org.virion.jam.panels;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * OptionsPanel.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: OptionsPanel.java 294 2006-04-14 10:28:11Z rambaut $
+ */
+public class OptionsPanel extends JPanel {
+
+ protected GridBagLayout gridbag = new GridBagLayout();
+ private final int hGap;
+ private final int vGap;
+
+ public OptionsPanel() {
+ this(4, 4);
+ }
+
+ public OptionsPanel(int hGap, int vGap) {
+ this.hGap = hGap;
+ this.vGap = vGap;
+
+ setBorder(BorderFactory.createEmptyBorder(3,6,6,6));
+ setLayout(gridbag);
+ setOpaque(false);
+ }
+
+ public void addLabel(String text) {
+
+ addSpanningComponent(new JLabel(text));
+ }
+
+ public void addSpanningComponent(JComponent comp) {
+
+ adjustComponent(comp);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(vGap / 2, 0, vGap / 2, 0);
+ c.weightx = 1.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+
+ // it is now up to the calling code to add a border
+ //comp.setBorder(new EmptyBorder(6, 24, 6, 24));
+ gridbag.setConstraints(comp, c);
+ add(comp);
+ }
+
+ /**
+ * This was a spelling mistake but remains here to avoid breaking
+ * too many things. Marked as depriciated
+ * @deprecated
+ */
+ public void addSeperator() {
+ addSeparator();
+ }
+
+ public void addSeparator() {
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(0,0,0,0);
+ c.weightx = 1.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ JSeparator separator = new JSeparator();
+ adjustComponent(separator);
+ separator.setOpaque(false);
+ gridbag.setConstraints(separator, c);
+ add(separator);
+ }
+
+ public void addComponent(JComponent comp) {
+ addComponent(comp, false);
+ }
+
+ public void addComponent(JComponent comp, boolean fill) {
+ JPanel panel = new JPanel();
+ panel.setOpaque(false);
+ addComponents(panel, false, comp, fill);
+ }
+
+ public JLabel addComponentWithLabel(String text, JComponent comp) {
+ return addComponentWithLabel(text, comp, false);
+ }
+
+ public JLabel addComponentWithLabel(String text, JComponent comp, boolean fill) {
+
+ JLabel label = new JLabel(text, javax.swing.SwingConstants.RIGHT);
+ label.setLabelFor(comp);
+ label.setOpaque(false);
+ addComponents(label, false, comp, fill);
+
+ return label;
+ }
+
+ public void addComponents(JComponent comp1, JComponent comp2) {
+ addComponents(comp1, false, comp2, false);
+ }
+
+ public void addComponents(JComponent comp1, boolean fill1, JComponent comp2, boolean fill2) {
+
+ adjustComponent(comp1);
+ adjustComponent(comp2);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.weightx = 0.0;
+ c.fill = fill1 ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE;
+
+ c.insets = new Insets(vGap / 2, 0, vGap / 2, hGap / 2);
+ c.gridwidth = GridBagConstraints.RELATIVE;
+ c.anchor = GridBagConstraints.EAST;
+ gridbag.setConstraints(comp1, c);
+ add(comp1);
+
+ c.insets = new Insets(vGap / 2, hGap / 2, vGap / 2, 0);
+ c.fill = fill2 ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.WEST;
+ gridbag.setConstraints(comp2, c);
+ add(comp2);
+ }
+
+ private void adjustComponent(JComponent comp) {
+ comp.putClientProperty("Quaqua.Component.visualMargin", new Insets(0,0,0,0));
+ if (comp.getFont() != null) {
+ comp.setFont(comp.getFont().deriveFont(11.0f));
+ }
+ }
+}
diff --git a/src/org/virion/jam/panels/RuleModel.java b/src/org/virion/jam/panels/RuleModel.java
new file mode 100644
index 0000000..9b86592
--- /dev/null
+++ b/src/org/virion/jam/panels/RuleModel.java
@@ -0,0 +1,43 @@
+/**
+ * RuleModel.java
+ */
+
+package org.virion.jam.panels;
+
+
+/**
+ * RuleModel.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: RuleModel.java 182 2006-01-23 21:24:01Z rambaut $
+ */
+
+
+public interface RuleModel {
+
+ /**
+ * Returns an array of strings to be presented as a combo box which
+ * are available fields to define rules on.
+ *
+ * @return the field names
+ */
+ Object[] getFields();
+
+ /**
+ * Returns an array of strings to be presented as a combo box which
+ * are possible rule conditions for the specified field.
+ *
+ * @return the condition names
+ */
+ Object[] getConditions(Object field);
+
+ /**
+ * Returns an array of strings to be presented as a combo box which
+ * are possible values for the field. Should return null if a text
+ * box is required.
+ *
+ * @return the values
+ */
+ Object[] getValues(Object field, Object condition);
+
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/panels/RulesPanel.java b/src/org/virion/jam/panels/RulesPanel.java
new file mode 100644
index 0000000..55c831d
--- /dev/null
+++ b/src/org/virion/jam/panels/RulesPanel.java
@@ -0,0 +1,201 @@
+/**
+ * RulesPanel.java
+ */
+
+package org.virion.jam.panels;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.*;
+import java.util.List;
+
+/**
+ * OptionsPanel.
+ *
+ * @author Andrew Rambaut
+ * @version $Id: RulesPanel.java 182 2006-01-23 21:24:01Z rambaut $
+ */
+
+
+public class RulesPanel extends JPanel {
+
+ private Icon addIcon = null;
+ private Icon addRolloverIcon = null;
+ private Icon addPressedIcon = null;
+ private Icon removeIcon = null;
+ private Icon removeRolloverIcon = null;
+ private Icon removePressedIcon = null;
+
+ public RulesPanel(RuleModel ruleModel) {
+ this.ruleModel = ruleModel;
+
+ try {
+ addIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/plus.png");
+ addRolloverIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/plusRollover.png");
+ addPressedIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/plusPressed.png");
+ removeIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/minus.png");
+ removeRolloverIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/minusRollover.png");
+ removePressedIcon = IconUtils.getIcon(RulesPanel.class, "images/plusminus/minusPressed.png");
+ } catch (Exception e) {
+ // do nothing
+ }
+
+ BoxLayout layout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
+ setLayout(layout);
+ setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+ setOpaque(false);
+ addRule(null);
+ Dimension dim = super.getPreferredSize();
+ dim.height += getComponent(0).getPreferredSize().getHeight();
+ setMinimumSize(dim);
+ setPreferredSize(dim);
+ }
+
+ public List getRules() {
+ return Collections.unmodifiableList(rules);
+ }
+
+ private void addRule(Rule previousRule) {
+
+ final DefaultRule rule = new DefaultRule();
+ RulePanel rulePanel = new RulePanel(rule);
+
+ if (previousRule != null) {
+ int index = rules.indexOf(previousRule);
+ add(rulePanel, index + 1);
+ rules.add(index + 1, rule);
+ } else {
+ add(rulePanel, 0);
+ rules.add(0, rule);
+ }
+ removeAction.setEnabled(rules.size() > 1);
+ validate();
+ repaint();
+ }
+
+ private void removeRule(Rule rule) {
+ int index = rules.indexOf(rule);
+ remove(index);
+ rules.remove(index);
+ removeAction.setEnabled(rules.size() > 1);
+ validate();
+ repaint();
+ }
+
+ public interface Rule {
+ Object getField();
+
+ Object getCondition();
+
+ Object getValue();
+ };
+
+ class DefaultRule implements Rule {
+ JComboBox fieldCombo;
+ JComboBox conditionCombo;
+ JTextField valueText;
+
+ public Object getField() {
+ return fieldCombo.getSelectedItem();
+ }
+
+ public Object getCondition() {
+ return conditionCombo.getSelectedItem();
+ }
+
+ public Object getValue() {
+ return valueText.getText();
+ }
+ };
+
+ class RulePanel extends JPanel {
+ RulePanel(final DefaultRule rule) {
+
+ setLayout(new GridBagLayout());
+
+ setOpaque(true);
+ setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.lightGray));
+ setBackground(new Color(0.0F, 0.0F, 0.0F, 0.05F));
+
+ rule.fieldCombo = new JComboBox(ruleModel.getFields());
+ rule.conditionCombo = new JComboBox(ruleModel.getConditions(rule.getField()));
+
+ rule.fieldCombo.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent ie) {
+ rule.conditionCombo.setModel(new DefaultComboBoxModel(ruleModel.getConditions(rule.getField())));
+ }
+ });
+ rule.valueText = new JTextField("");
+ rule.valueText.setColumns(12);
+
+ JButton addButton = new JButton("+");
+ addButton.putClientProperty("JButton.buttonType", "toolbar");
+// addButton.setBorderPainted(false);
+ addButton.setOpaque(false);
+ if (addIcon != null) {
+ addButton.setIcon(addIcon);
+ addButton.setPressedIcon(addPressedIcon);
+ addButton.setRolloverIcon(addRolloverIcon);
+ addButton.setRolloverEnabled(true);
+ addButton.setText(null);
+ }
+ addButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ addRule(rule);
+ }
+ });
+ addButton.setEnabled(true);
+
+ JButton removeButton = new JButton(removeAction);
+ removeButton.putClientProperty("JButton.buttonType", "toolbar");
+// removeButton.setBorderPainted(false);
+ removeButton.setOpaque(false);
+ if (removeIcon != null) {
+ removeButton.setIcon(removeIcon);
+ removeButton.setPressedIcon(removePressedIcon);
+ removeButton.setRolloverIcon(removeRolloverIcon);
+ removeButton.setRolloverEnabled(true);
+ removeButton.setText(null);
+ }
+ removeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ removeRule(rule);
+ }
+ });
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.0;
+ c.weighty = 0.0;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = new Insets(1, 2, 0, 2);
+ c.gridx = GridBagConstraints.RELATIVE;
+ add(rule.fieldCombo, c);
+ add(rule.conditionCombo, c);
+ c.weightx = 1.0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ add(rule.valueText, c);
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.0;
+ add(addButton, c);
+ add(removeButton, c);
+ }
+ };
+
+ AbstractAction removeAction = new AbstractAction("-") {
+ public void actionPerformed(ActionEvent ae) {
+ }
+ };
+
+
+ private RuleModel ruleModel;
+ private ArrayList rules = new ArrayList();
+
+
+}
diff --git a/src/org/virion/jam/panels/SearchPanel.java b/src/org/virion/jam/panels/SearchPanel.java
new file mode 100644
index 0000000..1bb7b38
--- /dev/null
+++ b/src/org/virion/jam/panels/SearchPanel.java
@@ -0,0 +1,281 @@
+package org.virion.jam.panels;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * @author Andrew Rambaut
+ * Date: Jul 26, 2004
+ * Time: 5:11:59 PM
+ */
+public class SearchPanel extends JPanel {
+
+ public SearchPanel(final String emptyLabel, final boolean searchAsYouType) {
+ this(emptyLabel, null, searchAsYouType);
+ }
+
+ public SearchPanel(final String emptyLabel, final JPopupMenu popup, final boolean searchAsYouType) {
+
+ this.emptyLabel = emptyLabel;
+ this.continuousSearch = searchAsYouType;
+
+ Icon findIcon = IconUtils.getIcon(SearchPanel.class, "images/search/find.png");
+ Icon findPopupIcon = IconUtils.getIcon(SearchPanel.class, "images/search/findPopup.png");
+ Icon stopIcon = IconUtils.getIcon(SearchPanel.class, "images/search/stop.png");
+ Icon stopRolloverIcon = IconUtils.getIcon(SearchPanel.class, "images/search/stopRollover.png");
+ Icon stopPressedIcon = IconUtils.getIcon(SearchPanel.class, "images/search/stopPressed.png");
+
+ setLayout(new BorderLayout(0, 0));
+
+ if (popup != null) {
+ popup.getSelectionModel().setSelectedIndex(0);
+ findButton = new JButton(findPopupIcon);
+ findButton.add(popup);
+ findButton.addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent mouseEvent) {
+ Component comp = mouseEvent.getComponent();
+ popup.show(comp, 0, comp.getHeight());
+ }
+ });
+ } else {
+ findButton = new JButton(findIcon);
+ findButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ searchText.requestFocusInWindow();
+ }
+ });
+ }
+
+ findButton.setPreferredSize(new Dimension(findButton.getIcon().getIconWidth(),
+ findButton.getIcon().getIconHeight()));
+
+ findButton.putClientProperty("JButton.buttonType", "toolbar");
+ findButton.setBorderPainted(false);
+ findButton.setOpaque(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ findButton.setContentAreaFilled(false);
+
+ JPanel findPanel = new JPanel();
+ findPanel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
+ findPanel.setOpaque(false);
+ findPanel.add(findButton);
+
+ searchText = new JTextField(emptyLabel);
+ searchText.setForeground(Color.lightGray);
+ searchText.setBorder(null);
+
+ cancelButton = new JButton(stopIcon);
+ cancelButton.setRolloverEnabled(true);
+ cancelButton.setRolloverIcon(stopRolloverIcon);
+ cancelButton.setPressedIcon(stopPressedIcon);
+ cancelButton.setPreferredSize(new Dimension(stopIcon.getIconWidth(), stopIcon.getIconHeight()));
+ cancelButton.putClientProperty("JButton.buttonType", "toolbar");
+ cancelButton.setBorderPainted(false);
+ cancelButton.setOpaque(false);
+ // this is required on Windows XP platform -- untested on Macintosh
+ cancelButton.setContentAreaFilled(false);
+
+ JPanel cancelPanel = new JPanel();
+ cancelPanel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
+ cancelPanel.setOpaque(false);
+ cancelPanel.add(cancelButton);
+
+ add(findPanel, BorderLayout.WEST);
+ add(searchText, BorderLayout.CENTER);
+ add(cancelPanel, BorderLayout.EAST);
+
+ setBackground(searchText.getBackground());
+ setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.gray));
+ setPreferredSize(new Dimension(120, 24));
+
+ searchText.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ if (searchTextEmpty) {
+ searchText.setText("");
+ searchText.setForeground(Color.black);
+ }
+ }
+
+ public void focusLost(FocusEvent e) {
+ checkSearchTextEmpty();
+ }
+ });
+
+ searchText.addCaretListener(new CaretListener() {
+ public void caretUpdate(CaretEvent e) {
+ }
+ });
+
+ searchText.getDocument().addDocumentListener(new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) {
+ searchTextChanged();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ searchTextChanged();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ searchTextChanged();
+ }
+ });
+
+ searchText.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (!searchTextEmpty) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ if (!continuousSearch) {
+ fireSearchStarted();
+ }
+ } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ clearSearchText();
+ }
+ }
+ if (e.getKeyCode() == KeyEvent.VK_DOWN) {
+ if (comboBox != null) {
+ int index = comboBox.getSelectedIndex();
+ if (index < comboBox.getItemCount() - 1)
+ index++;
+ comboBox.setSelectedIndex(index);
+ e.consume();
+ }
+ }
+ if (e.getKeyCode() == KeyEvent.VK_UP) {
+ if (comboBox != null) {
+ int index = comboBox.getSelectedIndex();
+ if (index > 0)
+ index--;
+ comboBox.setSelectedIndex(index);
+ e.consume();
+ }
+ }
+ }
+ });
+
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ clearSearchText();
+ checkSearchTextEmpty();
+ }
+ });
+ }
+
+ private void checkSearchTextEmpty() {
+ String text = searchText.getText().trim();
+ if (text.length() == 0) {
+ searchTextEmpty = true;
+ }
+ if (searchTextEmpty) {
+ searchText.setForeground(Color.lightGray);
+ searchText.setText(SearchPanel.this.emptyLabel);
+ }
+ }
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ findButton.setEnabled(enabled);
+ }
+
+ public void setToolTipText(String text) {
+ super.setToolTipText(text);
+ searchText.setToolTipText(text);
+ findButton.setToolTipText(text);
+ }
+
+ public void setFindIcon(Icon icon) {
+ findButton.setIcon(icon);
+ }
+
+ public void addSearchPanelListener(SearchPanelListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeDataSourceListener(SearchPanelListener listener) {
+ listeners.remove(listener);
+ }
+
+ public boolean requestFocusInWindow() {
+ return searchText.requestFocusInWindow();
+ }
+
+
+ public void removeAllDataSourceListeners() {
+ listeners.clear();
+ }
+
+ private void clearSearchText() {
+ searchText.setText("");
+ searchTextChanged();
+ }
+
+ private void fireSearchStarted() {
+ Iterator i = listeners.iterator();
+ while (i.hasNext()) {
+ ((SearchPanelListener) i.next()).searchStarted(searchText.getText());
+ }
+ }
+
+ private void fireSearchStopped() {
+ Iterator i = listeners.iterator();
+ while (i.hasNext()) {
+ ((SearchPanelListener) i.next()).searchStopped();
+ }
+ }
+
+ private ArrayList listeners = new ArrayList();
+ private boolean searchTextEmpty = true;
+ private final String emptyLabel;
+ private boolean continuousSearch;
+ private final JButton findButton;
+ private final JTextField searchText;
+ private final JButton cancelButton;
+
+ private JComboBox comboBox;
+
+ public JComboBox getComboBox() {
+ return comboBox;
+ }
+
+ public void setComboBox(JComboBox comboBox) {
+ this.comboBox = comboBox;
+ if (comboBox != null) {
+ comboBox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ requestFocusInWindow();
+ searchTextChanged();
+ }
+ });
+ }
+ }
+
+ private void searchTextChanged() {
+ if (searchText.isFocusOwner())
+ searchTextEmpty = searchText.getText().length() == 0;
+ fireSearchTextChanged();
+ }
+
+ public void fireSearchTextChanged() {
+ if (searchTextEmpty) {
+ cancelButton.setVisible(false);
+ if (continuousSearch) {
+ fireSearchStopped();
+ }
+ } else {
+ cancelButton.setVisible(true);
+ if (continuousSearch) {
+ fireSearchStarted();
+ }
+ }
+ }
+}
+
+
diff --git a/src/org/virion/jam/panels/SearchPanelListener.java b/src/org/virion/jam/panels/SearchPanelListener.java
new file mode 100644
index 0000000..6e1afd1
--- /dev/null
+++ b/src/org/virion/jam/panels/SearchPanelListener.java
@@ -0,0 +1,26 @@
+package org.virion.jam.panels;
+
+/**
+ * An interface for listeners to the SearchPanel class.
+ * @author Andrew Rambaut
+ * Date: Jul 26, 2004
+ * Time: 5:37:15 PM
+ */
+public interface SearchPanelListener {
+
+ /**
+ * Called when the user requests a search by pressing return having
+ * typed a search string into the text field. If the continuousUpdate
+ * flag is true then this method is called when the user types into
+ * the text field.
+ * @param searchString the user's search string
+ */
+ void searchStarted(String searchString);
+
+ /**
+ * Called when the user presses the cancel search button or presses
+ * escape while the search is in focus.
+ */
+ void searchStopped();
+
+}
diff --git a/src/org/virion/jam/panels/StatusBar.java b/src/org/virion/jam/panels/StatusBar.java
new file mode 100644
index 0000000..c6ee1e1
--- /dev/null
+++ b/src/org/virion/jam/panels/StatusBar.java
@@ -0,0 +1,28 @@
+package org.virion.jam.panels;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author rambaut
+ * Date: Oct 12, 2004
+ * Time: 12:18:09 AM
+ */
+public class StatusBar extends StatusPanel {
+ public StatusBar(String initialText) {
+ super(initialText);
+
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray),
+ BorderFactory.createEmptyBorder(2, 12, 2, 12)));
+ // panel.setBackground(new Color(0.0F, 0.0F, 0.0F, 0.05F));
+
+ }
+
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(new Color(0.0F, 0.0F, 0.0F, 0.05F));
+ g.fillRect(0, 0, getWidth(), getHeight());
+ }
+
+}
diff --git a/src/org/virion/jam/panels/StatusListener.java b/src/org/virion/jam/panels/StatusListener.java
new file mode 100644
index 0000000..baa6be6
--- /dev/null
+++ b/src/org/virion/jam/panels/StatusListener.java
@@ -0,0 +1,17 @@
+package org.virion.jam.panels;
+
+/**
+ * @author rambaut
+ * Date: Jul 27, 2004
+ * Time: 10:04:04 AM
+ */
+public interface StatusListener {
+
+ /**
+ * Called when the status is to be changed.
+ * @param status the status constant
+ * @param statusText the status text
+ */
+ void statusChanged(int status, String statusText);
+
+}
diff --git a/src/org/virion/jam/panels/StatusPanel.java b/src/org/virion/jam/panels/StatusPanel.java
new file mode 100644
index 0000000..add03b2
--- /dev/null
+++ b/src/org/virion/jam/panels/StatusPanel.java
@@ -0,0 +1,240 @@
+package org.virion.jam.panels;
+
+import org.virion.jam.util.IconUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * @author Andrew Rambaut
+ * Date: Jul 26, 2004
+ * Time: 5:11:59 PM
+ */
+public class StatusPanel extends JPanel implements StatusListener {
+
+ public static final int NORMAL = 0;
+ public static final int WORKING = 1;
+ public static final int WARNING = 2;
+ public static final int ERROR = 3;
+
+ private JLabel statusLabel = null;
+ private String statusText = " ";
+ private JButton statusButton;
+ private int status = - 1;
+
+ private StatusProvider statusProvider = null;
+ private Timer timer = null;
+ private long lastUpdate;
+ private boolean pendingUpdate = false;
+ private String pendingText ="";
+
+ public StatusPanel() {
+ this(null);
+ }
+
+ public StatusPanel(String initialText) {
+
+ setLayout(new BorderLayout(4,4));
+
+ if (initialText != null) {
+ statusText = initialText;
+
+ statusLabel = new JLabel(statusText);
+ add(statusLabel, BorderLayout.CENTER);
+
+ // Added this to reduce size of status bar (this looks nice on a Mac - not tested on Windows).
+ statusLabel.setFont(UIManager.getFont("SmallSystemFont"));
+
+ }
+
+ statusButton = new JButton(normalStatusIcon);
+ statusButton.setPreferredSize(new Dimension(16,16));
+
+ statusButton.putClientProperty("JButton.buttonType", "toolbar");
+ statusButton.setBorderPainted(false);
+ statusButton.setOpaque(false);
+ statusButton.setRolloverEnabled(true);
+ // this is required on Windows XP platform -- untested on Macintosh
+ // ... seems to be fine on a Macintosh (AR).
+ statusButton.setContentAreaFilled(false);
+
+ statusButton.addActionListener(new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ statusButtonPressed();
+ }
+ });
+
+ add(statusButton, BorderLayout.WEST);
+ }
+
+ public void setStatusProvider(StatusProvider statusProvider) {
+ stopAnimation();
+
+ if (this.statusProvider != null) {
+ this.statusProvider.removeStatusListener(this);
+ }
+ this.statusProvider = statusProvider;
+ if (this.statusProvider != null) {
+ this.statusProvider.addStatusListener(this);
+ }
+ }
+
+ public void statusChanged(int status, String statusText) {
+ //updating the status text too often is a very bad idea.
+ //on my machine doing 30 updates per second fully utilises the CPU.
+ //so we limit it here to five updates per second.
+ boolean updateText = (System.currentTimeMillis() - lastUpdate)> 200;
+ if(this.status != status) updateText = true;
+ if(statusText.length()> 0 && (this.statusText.length() == 0 || this.statusText.equals(" ")))
+ updateText = true;
+ setStatus(status);
+ if(updateText) {
+ setStatusText(statusText);
+ lastUpdate = System.currentTimeMillis();
+ }
+ else {
+ pendingUpdate = true;
+ pendingText = statusText;
+ }
+ }
+
+ private void setStatusText(final String statusText) {
+
+ if (statusText == null || statusText.length() == 0) {
+ this.statusText = " ";
+ } else {
+ this.statusText = statusText;
+ }
+ if (statusLabel != null) {
+ //it is only safe to call "statusLabel.setText(statusText);" in the AWT thread:
+ invokeNow(new Runnable() {
+ public void run() {
+ statusLabel.setText(statusText);
+ }
+ });
+
+ }
+ }
+
+ private void invokeNow(Runnable runnable) {
+ if (EventQueue.isDispatchThread()) {
+ runnable.run();
+ } else {
+ /*try {*/
+ EventQueue.invokeLater(runnable);
+ /*} catch (InterruptedException e) {
+ } catch (InvocationTargetException e) {
+ }*/
+ }
+ }
+
+
+ private void setStatus(final int status) {
+ if(status == this.status) return;
+ this.status = status;
+ stopAnimation();
+ Runnable runnable =new Runnable() {
+ public void run() {
+ switch (status) {
+ case NORMAL:
+ statusButton.setIcon(normalStatusIcon);
+ statusButton.setPressedIcon(normalStatusPressedIcon);
+ statusButton.setRolloverIcon(normalStatusRolloverIcon);
+ break;
+ case WORKING:
+ startAnimation();
+ break;
+ case WARNING:
+ statusButton.setIcon(warningStatusIcon);
+ statusButton.setPressedIcon(warningStatusPressedIcon);
+ statusButton.setRolloverIcon(warningStatusRolloverIcon);
+ break;
+ case ERROR:
+ statusButton.setIcon(errorStatusIcon);
+ statusButton.setPressedIcon(errorStatusPressedIcon);
+ statusButton.setRolloverIcon(errorStatusRolloverIcon);
+ break;
+ }
+ }
+ };
+ invokeNow(runnable);
+ }
+
+ private void statusButtonPressed() {
+ if (statusProvider != null) {
+ statusProvider.fireStatusButtonPressed();
+ }
+ }
+
+ private void startAnimation() {
+ if (workingStatusIcons == null) return;
+ listener.actionPerformed(null);
+
+ timer = new javax.swing.Timer(100, listener);
+ timer.setCoalesce(true);
+ timer.start();
+ }
+ private void pendingCheck () {
+
+ if (pendingUpdate) {
+ setStatusText(pendingText);
+ }
+ pendingUpdate = false;
+ }
+
+ private void stopAnimation() {
+ pendingCheck ();
+ current = 0;
+ if (timer == null) return;
+ timer.stop();
+ }
+
+ private int current = 0;
+ ActionListener listener = new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ statusButton.setIcon(workingStatusIcons[current]);
+ current++;
+ if (current >= workingStatusIcons.length) current = 0;
+ pendingCheck ();
+ }
+ };
+
+ private static Icon normalStatusIcon = null;
+ private static Icon normalStatusPressedIcon = null;
+ private static Icon normalStatusRolloverIcon = null;
+
+ private static Icon[] workingStatusIcons = null;
+
+ private static Icon warningStatusIcon = null;
+ private static Icon warningStatusPressedIcon = null;
+ private static Icon warningStatusRolloverIcon = null;
+
+ private static Icon errorStatusIcon = null;
+ private static Icon errorStatusPressedIcon = null;
+ private static Icon errorStatusRolloverIcon = null;
+
+ static {
+ try {
+ workingStatusIcons = new Icon[12];
+
+ workingStatusIcons[0] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity1.png");
+ workingStatusIcons[1] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity2.png");
+ workingStatusIcons[2] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity3.png");
+ workingStatusIcons[3] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity4.png");
+ workingStatusIcons[4] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity5.png");
+ workingStatusIcons[5] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity6.png");
+ workingStatusIcons[6] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity7.png");
+ workingStatusIcons[7] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity8.png");
+ workingStatusIcons[8] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity9.png");
+ workingStatusIcons[9] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity10.png");
+ workingStatusIcons[10] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity11.png");
+ workingStatusIcons[11] = IconUtils.getIcon(StatusPanel.class, "images/activity/activity12.png");
+ } catch (Exception e) {
+ workingStatusIcons = null;
+ }
+ }
+}
diff --git a/src/org/virion/jam/panels/StatusProvider.java b/src/org/virion/jam/panels/StatusProvider.java
new file mode 100644
index 0000000..3ceaa5a
--- /dev/null
+++ b/src/org/virion/jam/panels/StatusProvider.java
@@ -0,0 +1,144 @@
+package org.virion.jam.panels;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * @author rambaut
+ * Date: Jul 27, 2004
+ * Time: 9:32:24 AM
+ */
+public interface StatusProvider {
+
+ /**
+ * Status providers must be able to store a list of StatusListeners. They should
+ * then call the appropriate methods on all of these to update the status.
+ *
+ * @param statusListener the StatusListener to be added
+ */
+ void addStatusListener(StatusListener statusListener);
+
+ /**
+ * Remove the given StatusListener from the provider's list.
+ *
+ * @param statusListener the StatusListener to be removed
+ */
+ void removeStatusListener(StatusListener statusListener);
+
+ void fireStatusChanged(int status, String statusText);
+
+ /**
+ * The status bar has been pressed. This method should not really be
+ * here. You should instead call {@link #fireStatusButtonPressed()}
+ */
+ void statusButtonPressed();
+
+ String getStatusText();
+ int getStatus();
+
+ /**
+ * Fire a status bar event to anything interested.
+ */
+ void fireStatusButtonPressed();
+
+ public void addOverrideProvider(StatusProvider provider);
+ public void removeOverrideProvider(StatusProvider provider);
+
+
+ public class Helper implements StatusProvider {
+ private int lastStatus = StatusPanel.NORMAL;
+ private String lastStatusText = "";
+ private ArrayList listeners = new ArrayList();
+
+ public synchronized void addStatusListener(StatusListener statusListener) {
+ statusListener.statusChanged(lastStatus, lastStatusText);
+ listeners.add(statusListener);
+ }
+
+ public synchronized void removeStatusListener(StatusListener statusListener) {
+ statusListener.statusChanged(StatusPanel.NORMAL, "");
+ listeners.remove(statusListener);
+ }
+
+ private synchronized void realFireStatusChanged() {
+ int status = lastStatus;
+ String statusText = lastStatusText;
+ int index = overrideProviders.size()- 1;
+ if (index >= 0) {
+ StatusProvider override =(StatusProvider) overrideProviders.get(index);
+ status = override.getStatus();
+ statusText = override.getStatusText();
+ }
+ Iterator i = listeners.iterator();
+ while (i.hasNext()) {
+ ((StatusListener) i.next()).statusChanged(status, statusText);
+ }
+
+ }
+
+ public synchronized void fireStatusChanged(int status, String statusText) {
+ lastStatus = status;
+ lastStatusText = statusText;
+ realFireStatusChanged();
+ }
+
+ private ArrayList overrideListeners = new ArrayList ();
+ private ArrayList overrideProviders = new ArrayList ();
+
+ public synchronized void addOverrideProvider(final StatusProvider provider) {
+ StatusListener listener = new StatusListener(){
+
+ public void statusChanged(int status, String statusText) {
+ synchronized(StatusProvider.Helper.this) {
+ if (overrideProviders.size() > 0 && overrideProviders.get(overrideProviders.size()- 1)== provider) {
+ realFireStatusChanged();
+ }
+ }
+ }
+ };
+ provider.addStatusListener(listener);
+ overrideProviders.add(provider);
+ overrideListeners.add(listener);
+ }
+
+ public synchronized void removeOverrideProvider(StatusProvider provider) {
+ for (int i = 0; i < overrideProviders.size(); i++) {
+ if(overrideProviders.get(i) == provider) {
+ overrideProviders.remove(i);
+ StatusListener listener =(StatusListener) overrideListeners.get(i);
+ provider.removeStatusListener(listener);
+ overrideListeners.remove(i);
+ break;
+ }
+ }
+ realFireStatusChanged();
+ }
+ public synchronized void fireStatusButtonPressed(){
+ int index = overrideProviders.size ();
+ if (index > 0) {
+ ((StatusProvider)overrideProviders.get(index - 1)).fireStatusButtonPressed();
+ }
+ else {
+ statusButtonPressed();
+ }
+ }
+
+ public void statusButtonPressed() {
+ // do nothing... override
+ }
+
+ public synchronized int getStatus() {
+ return lastStatus;
+ }
+
+ public synchronized String getStatusText() {
+ return lastStatusText;
+ }
+
+ public synchronized boolean hasOverrideProvider() {
+ return !overrideProviders.isEmpty();
+ }
+ }
+
+
+}
diff --git a/src/org/virion/jam/panels/images/action/actionButton.png b/src/org/virion/jam/panels/images/action/actionButton.png
new file mode 100644
index 0000000..bf76bb6
Binary files /dev/null and b/src/org/virion/jam/panels/images/action/actionButton.png differ
diff --git a/src/org/virion/jam/panels/images/action/actionButtonInactive.png b/src/org/virion/jam/panels/images/action/actionButtonInactive.png
new file mode 100644
index 0000000..257a70f
Binary files /dev/null and b/src/org/virion/jam/panels/images/action/actionButtonInactive.png differ
diff --git a/src/org/virion/jam/panels/images/action/actionButtonPressed.png b/src/org/virion/jam/panels/images/action/actionButtonPressed.png
new file mode 100644
index 0000000..e8ec793
Binary files /dev/null and b/src/org/virion/jam/panels/images/action/actionButtonPressed.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity1.png b/src/org/virion/jam/panels/images/activity/activity1.png
new file mode 100644
index 0000000..1f42298
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity1.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity10.png b/src/org/virion/jam/panels/images/activity/activity10.png
new file mode 100644
index 0000000..b02241c
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity10.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity11.png b/src/org/virion/jam/panels/images/activity/activity11.png
new file mode 100644
index 0000000..ff7cbc1
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity11.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity12.png b/src/org/virion/jam/panels/images/activity/activity12.png
new file mode 100644
index 0000000..b0fd9c7
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity12.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity2.png b/src/org/virion/jam/panels/images/activity/activity2.png
new file mode 100644
index 0000000..437873d
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity2.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity3.png b/src/org/virion/jam/panels/images/activity/activity3.png
new file mode 100644
index 0000000..433a514
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity3.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity4.png b/src/org/virion/jam/panels/images/activity/activity4.png
new file mode 100644
index 0000000..db31289
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity4.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity5.png b/src/org/virion/jam/panels/images/activity/activity5.png
new file mode 100644
index 0000000..4ebb491
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity5.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity6.png b/src/org/virion/jam/panels/images/activity/activity6.png
new file mode 100644
index 0000000..1730405
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity6.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity7.png b/src/org/virion/jam/panels/images/activity/activity7.png
new file mode 100644
index 0000000..b84755f
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity7.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity8.png b/src/org/virion/jam/panels/images/activity/activity8.png
new file mode 100644
index 0000000..0e5e635
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity8.png differ
diff --git a/src/org/virion/jam/panels/images/activity/activity9.png b/src/org/virion/jam/panels/images/activity/activity9.png
new file mode 100644
index 0000000..4d79abb
Binary files /dev/null and b/src/org/virion/jam/panels/images/activity/activity9.png differ
diff --git a/src/org/virion/jam/panels/images/add/addButton.png b/src/org/virion/jam/panels/images/add/addButton.png
new file mode 100644
index 0000000..3a82116
Binary files /dev/null and b/src/org/virion/jam/panels/images/add/addButton.png differ
diff --git a/src/org/virion/jam/panels/images/add/addButtonInactive.png b/src/org/virion/jam/panels/images/add/addButtonInactive.png
new file mode 100644
index 0000000..4edf2d1
Binary files /dev/null and b/src/org/virion/jam/panels/images/add/addButtonInactive.png differ
diff --git a/src/org/virion/jam/panels/images/add/addButtonPressed.png b/src/org/virion/jam/panels/images/add/addButtonPressed.png
new file mode 100644
index 0000000..41a09f1
Binary files /dev/null and b/src/org/virion/jam/panels/images/add/addButtonPressed.png differ
diff --git a/src/org/virion/jam/panels/images/network/network1.png b/src/org/virion/jam/panels/images/network/network1.png
new file mode 100644
index 0000000..8059723
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network1.png differ
diff --git a/src/org/virion/jam/panels/images/network/network2.png b/src/org/virion/jam/panels/images/network/network2.png
new file mode 100644
index 0000000..bfd46f0
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network2.png differ
diff --git a/src/org/virion/jam/panels/images/network/network3.png b/src/org/virion/jam/panels/images/network/network3.png
new file mode 100644
index 0000000..9b615ec
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network3.png differ
diff --git a/src/org/virion/jam/panels/images/network/network4.png b/src/org/virion/jam/panels/images/network/network4.png
new file mode 100644
index 0000000..ffdfc5f
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network4.png differ
diff --git a/src/org/virion/jam/panels/images/network/network5.png b/src/org/virion/jam/panels/images/network/network5.png
new file mode 100644
index 0000000..4bd05ee
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network5.png differ
diff --git a/src/org/virion/jam/panels/images/network/network6.png b/src/org/virion/jam/panels/images/network/network6.png
new file mode 100644
index 0000000..d68474e
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network6.png differ
diff --git a/src/org/virion/jam/panels/images/network/network7.png b/src/org/virion/jam/panels/images/network/network7.png
new file mode 100644
index 0000000..54efc48
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network7.png differ
diff --git a/src/org/virion/jam/panels/images/network/network8.png b/src/org/virion/jam/panels/images/network/network8.png
new file mode 100644
index 0000000..01d8813
Binary files /dev/null and b/src/org/virion/jam/panels/images/network/network8.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/minus.png b/src/org/virion/jam/panels/images/plusminus/minus.png
new file mode 100644
index 0000000..6d73c32
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/minus.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/minusPressed.png b/src/org/virion/jam/panels/images/plusminus/minusPressed.png
new file mode 100644
index 0000000..a94ab0c
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/minusPressed.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/minusRollover.png b/src/org/virion/jam/panels/images/plusminus/minusRollover.png
new file mode 100644
index 0000000..563a618
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/minusRollover.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/plus.png b/src/org/virion/jam/panels/images/plusminus/plus.png
new file mode 100644
index 0000000..ba80302
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/plus.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/plusPressed.png b/src/org/virion/jam/panels/images/plusminus/plusPressed.png
new file mode 100644
index 0000000..96af694
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/plusPressed.png differ
diff --git a/src/org/virion/jam/panels/images/plusminus/plusRollover.png b/src/org/virion/jam/panels/images/plusminus/plusRollover.png
new file mode 100644
index 0000000..afd9281
Binary files /dev/null and b/src/org/virion/jam/panels/images/plusminus/plusRollover.png differ
diff --git a/src/org/virion/jam/panels/images/remove/removeButton.png b/src/org/virion/jam/panels/images/remove/removeButton.png
new file mode 100644
index 0000000..b9982f2
Binary files /dev/null and b/src/org/virion/jam/panels/images/remove/removeButton.png differ
diff --git a/src/org/virion/jam/panels/images/remove/removeButtonInactive.png b/src/org/virion/jam/panels/images/remove/removeButtonInactive.png
new file mode 100644
index 0000000..3f6122e
Binary files /dev/null and b/src/org/virion/jam/panels/images/remove/removeButtonInactive.png differ
diff --git a/src/org/virion/jam/panels/images/remove/removeButtonPressed.png b/src/org/virion/jam/panels/images/remove/removeButtonPressed.png
new file mode 100644
index 0000000..13e8eaa
Binary files /dev/null and b/src/org/virion/jam/panels/images/remove/removeButtonPressed.png differ
diff --git a/src/org/virion/jam/panels/images/search/find.png b/src/org/virion/jam/panels/images/search/find.png
new file mode 100644
index 0000000..8a84182
Binary files /dev/null and b/src/org/virion/jam/panels/images/search/find.png differ
diff --git a/src/org/virion/jam/panels/images/search/findPopup.png b/src/org/virion/jam/panels/images/search/findPopup.png
new file mode 100644
index 0000000..c12cb06
Binary files /dev/null and b/src/org/virion/jam/panels/images/search/findPopup.png differ
diff --git a/src/org/virion/jam/panels/images/search/stop.png b/src/org/virion/jam/panels/images/search/stop.png
new file mode 100644
index 0000000..11196c4
Binary files /dev/null and b/src/org/virion/jam/panels/images/search/stop.png differ
diff --git a/src/org/virion/jam/panels/images/search/stopPressed.png b/src/org/virion/jam/panels/images/search/stopPressed.png
new file mode 100644
index 0000000..b8a4e84
Binary files /dev/null and b/src/org/virion/jam/panels/images/search/stopPressed.png differ
diff --git a/src/org/virion/jam/panels/images/search/stopRollover.png b/src/org/virion/jam/panels/images/search/stopRollover.png
new file mode 100644
index 0000000..92a2ed6
Binary files /dev/null and b/src/org/virion/jam/panels/images/search/stopRollover.png differ
diff --git a/src/org/virion/jam/panels/images/status/resume.png b/src/org/virion/jam/panels/images/status/resume.png
new file mode 100644
index 0000000..d1fde4d
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/resume.png differ
diff --git a/src/org/virion/jam/panels/images/status/resumePressed.png b/src/org/virion/jam/panels/images/status/resumePressed.png
new file mode 100644
index 0000000..8018fef
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/resumePressed.png differ
diff --git a/src/org/virion/jam/panels/images/status/resumeRollover.png b/src/org/virion/jam/panels/images/status/resumeRollover.png
new file mode 100644
index 0000000..02c2dba
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/resumeRollover.png differ
diff --git a/src/org/virion/jam/panels/images/status/reveal.png b/src/org/virion/jam/panels/images/status/reveal.png
new file mode 100644
index 0000000..ab241ac
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/reveal.png differ
diff --git a/src/org/virion/jam/panels/images/status/revealPressed.png b/src/org/virion/jam/panels/images/status/revealPressed.png
new file mode 100644
index 0000000..dddb132
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/revealPressed.png differ
diff --git a/src/org/virion/jam/panels/images/status/revealRollover.png b/src/org/virion/jam/panels/images/status/revealRollover.png
new file mode 100644
index 0000000..e0724de
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/revealRollover.png differ
diff --git a/src/org/virion/jam/panels/images/status/stop.png b/src/org/virion/jam/panels/images/status/stop.png
new file mode 100644
index 0000000..11196c4
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/stop.png differ
diff --git a/src/org/virion/jam/panels/images/status/stopPressed.png b/src/org/virion/jam/panels/images/status/stopPressed.png
new file mode 100644
index 0000000..b8a4e84
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/stopPressed.png differ
diff --git a/src/org/virion/jam/panels/images/status/stopRollover.png b/src/org/virion/jam/panels/images/status/stopRollover.png
new file mode 100644
index 0000000..92a2ed6
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/stopRollover.png differ
diff --git a/src/org/virion/jam/panels/images/status/warning.png b/src/org/virion/jam/panels/images/status/warning.png
new file mode 100644
index 0000000..46a7d2d
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/warning.png differ
diff --git a/src/org/virion/jam/panels/images/status/warningPressed.png b/src/org/virion/jam/panels/images/status/warningPressed.png
new file mode 100644
index 0000000..46f047d
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/warningPressed.png differ
diff --git a/src/org/virion/jam/panels/images/status/warningRollover.png b/src/org/virion/jam/panels/images/status/warningRollover.png
new file mode 100644
index 0000000..6443dc3
Binary files /dev/null and b/src/org/virion/jam/panels/images/status/warningRollover.png differ
diff --git a/src/org/virion/jam/preferences/PreferencesDialog.java b/src/org/virion/jam/preferences/PreferencesDialog.java
new file mode 100644
index 0000000..4ebe291
--- /dev/null
+++ b/src/org/virion/jam/preferences/PreferencesDialog.java
@@ -0,0 +1,122 @@
+/*
+ * DemographicDialog.java
+ *
+ * (c) 2002-2005 BEAST Development Core Team
+ *
+ * This package may be distributed under the
+ * Lesser Gnu Public Licence (LGPL)
+ */
+package org.virion.jam.preferences;
+
+import org.virion.jam.toolbar.*;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.*;
+import java.util.List;
+
+/**
+ * DemographicDialog.java
+ *
+ * Title: Tracer
+ * Description: An application for analysing MCMC trace files.
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: PreferencesDialog.java 724 2007-06-11 16:25:39Z rambaut $
+ */
+public class PreferencesDialog {
+
+ private JFrame frame;
+ private CardLayout cardLayout;
+ private JPanel sectionsPanel;
+
+ public PreferencesDialog(JFrame frame) {
+ this.frame = frame;
+ }
+
+ public void showDialog() {
+
+ JPanel panel = new JPanel(new BorderLayout());
+ Toolbar toolbar = new Toolbar(null);
+ toolbar.setFloatable(false);
+
+ cardLayout = new CardLayout();
+ sectionsPanel = new JPanel(cardLayout);
+ sectionsPanel.setBorder(new EmptyBorder(12,12,12,12));
+
+ panel.add(toolbar, BorderLayout.NORTH);
+ panel.add(sectionsPanel, BorderLayout.CENTER);
+
+ JOptionPane optionPane = new JOptionPane(panel,
+ JOptionPane.PLAIN_MESSAGE,
+ JOptionPane.DEFAULT_OPTION,
+ null,
+ new String[] { "Done" },
+ null);
+ optionPane.setBorder(new EmptyBorder(0,0,12,0));
+
+ final JDialog dialog = optionPane.createDialog(frame, currentSection);
+
+ for (PreferencesSection section : sections) {
+ final String title = section.getTitle();
+ if (currentSection == null) {
+ currentSection = title;
+ }
+ final ToolbarButton button = new ToolbarButton(
+ new ToolbarAction(title, title, section.getIcon()) {
+ public void actionPerformed(ActionEvent e) {
+ showSection(title);
+ currentSection = title;
+ dialog.setTitle(currentSection);
+ }
+ }
+ );
+ JPanel buttonPanel = new JPanel(new BorderLayout());
+ buttonPanel.setBorder(BorderFactory.createEmptyBorder(0,1,0,1));
+ buttonPanel.add(button, BorderLayout.CENTER);
+
+ toolbar.addComponent(buttonPanel);
+ sectionsPanel.add(section.getPanel(), title);
+ buttons.put(title, buttonPanel);
+
+ section.retrievePreferences();
+ }
+ toolbar.addFlexibleSpace();
+
+ showSection(currentSection);
+
+ dialog.pack();
+ dialog.setVisible(true);
+
+ for (PreferencesSection section : sections) {
+ section.storePreferences();
+ }
+ }
+
+ public void showSection(String title) {
+ cardLayout.show(sectionsPanel, title);
+
+ JPanel buttonPanel = buttons.get(currentSection);
+ buttonPanel.setBorder(BorderFactory.createEmptyBorder(0,1,0,1));
+ buttonPanel.setOpaque(false);
+
+ buttonPanel = buttons.get(title);
+ buttonPanel.setBorder(BorderFactory.createMatteBorder(0,1,0,1,Color.gray));
+ buttonPanel.setBackground(new Color(0.85F, 0.85F, 0.85F, 0.5F));
+ buttonPanel.setOpaque(true);
+ buttonPanel.repaint();
+ }
+
+
+ public void addSection(PreferencesSection section) {
+ sections.add(section);
+ section.retrievePreferences();
+ }
+
+ String currentSection = null;
+
+ private List<PreferencesSection> sections = new ArrayList<PreferencesSection>();
+ private Map<String, JPanel> buttons = new HashMap<String, JPanel>();
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/preferences/PreferencesSection.java b/src/org/virion/jam/preferences/PreferencesSection.java
new file mode 100644
index 0000000..0c96934
--- /dev/null
+++ b/src/org/virion/jam/preferences/PreferencesSection.java
@@ -0,0 +1,20 @@
+package org.virion.jam.preferences;
+
+import javax.swing.*;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ * @version $Id: PreferencesSection.java 275 2006-03-22 16:58:56Z rambaut $
+ */
+public interface PreferencesSection {
+ String getTitle();
+
+ Icon getIcon();
+
+ JPanel getPanel();
+
+ void retrievePreferences();
+
+ void storePreferences();
+}
diff --git a/src/org/virion/jam/table/AdvancedTableUI.java b/src/org/virion/jam/table/AdvancedTableUI.java
new file mode 100644
index 0000000..3199940
--- /dev/null
+++ b/src/org/virion/jam/table/AdvancedTableUI.java
@@ -0,0 +1,42 @@
+package org.virion.jam.table;
+
+import javax.swing.plaf.basic.BasicTableUI;
+import javax.swing.event.MouseInputListener;
+import javax.swing.*;
+import java.awt.event.MouseEvent;
+import java.awt.*;
+
+/**
+ * @author rambaut
+ * Date: Oct 20, 2004
+ * Time: 10:16:52 PM
+ */
+public class AdvancedTableUI extends BasicTableUI {
+
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ if (org.virion.jam.mac.Utils.isMacOSX()) {
+ c.setFont(new Font("Lucida Grande", Font.PLAIN, 9));
+ }
+ }
+
+ protected MouseInputListener createMouseInputListener() {
+ return new AdvancedTableUI.AdvancedMouseInputHandler();
+ }
+
+ class AdvancedMouseInputHandler extends MouseInputHandler {
+ public void mousePressed(MouseEvent e) {
+ Point origin = e.getPoint();
+ int row = table.rowAtPoint(origin);
+ int column = table.columnAtPoint(origin);
+ if (row != -1 && column != -1) {
+ if (table.isCellSelected(row, column)) {
+ e.consume();
+ }
+ }
+
+ super.mousePressed(e);
+ }
+ }
+}
diff --git a/src/org/virion/jam/table/ColorEditor.java b/src/org/virion/jam/table/ColorEditor.java
new file mode 100644
index 0000000..f19a69c
--- /dev/null
+++ b/src/org/virion/jam/table/ColorEditor.java
@@ -0,0 +1,78 @@
+/*
+ * ColorEditor.java (compiles with releases 1.3 and 1.4) is used by
+ * TableDialogEditDemo.java.
+ */
+
+package org.virion.jam.table;
+
+import javax.swing.*;
+import javax.swing.table.TableCellEditor;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class ColorEditor extends AbstractCellEditor
+ implements TableCellEditor,
+ ActionListener {
+ Color currentColor;
+ JButton button;
+ JColorChooser colorChooser;
+ JDialog dialog;
+ protected static final String EDIT = "edit";
+
+ public ColorEditor() {
+ //Set up the editor (from the table's point of view),
+ //which is a button.
+ //This button brings up the color chooser dialog,
+ //which is the editor from the user's point of view.
+ button = new JButton();
+ button.setActionCommand(EDIT);
+ button.addActionListener(this);
+ button.setBorderPainted(false);
+
+ //Set up the dialog that the button brings up.
+ colorChooser = new JColorChooser();
+ dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true, //modal
+ colorChooser,
+ this, //OK button handler
+ null); //no CANCEL button handler
+ }
+
+ /**
+ * Handles events from the editor button and from
+ * the dialog's OK button.
+ */
+ public void actionPerformed(ActionEvent e) {
+ if (EDIT.equals(e.getActionCommand())) {
+ //The user has clicked the cell, so
+ //bring up the dialog.
+ button.setBackground(currentColor);
+ colorChooser.setColor(currentColor);
+ dialog.setVisible(true);
+
+ //Make the renderer reappear.
+ fireEditingStopped();
+
+ } else { //User pressed dialog's "OK" button.
+ currentColor = colorChooser.getColor();
+ }
+ }
+
+ //Implement the one CellEditor method that AbstractCellEditor doesn't.
+ public Object getCellEditorValue() {
+ return currentColor;
+ }
+
+ //Implement the one method defined by TableCellEditor.
+ public Component getTableCellEditorComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column) {
+ currentColor = (Color) value;
+ return button;
+ }
+}
+
diff --git a/src/org/virion/jam/table/ColorRenderer.java b/src/org/virion/jam/table/ColorRenderer.java
new file mode 100644
index 0000000..3798147
--- /dev/null
+++ b/src/org/virion/jam/table/ColorRenderer.java
@@ -0,0 +1,50 @@
+/*
+ * ColorRenderer.java (compiles with releases 1.2, 1.3, and 1.4) is used by
+ * TableDialogEditDemo.java.
+ */
+
+package org.virion.jam.table;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+
+public class ColorRenderer extends JLabel
+ implements TableCellRenderer {
+ Border unselectedBorder = null;
+ Border selectedBorder = null;
+ boolean isBordered = true;
+
+ public ColorRenderer(boolean isBordered) {
+ 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) {
+ Color newColor = (Color) color;
+ setBackground(newColor);
+ 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);
+ }
+ }
+
+ setToolTipText("RGB value: " + newColor.getRed() + ", "
+ + newColor.getGreen() + ", "
+ + newColor.getBlue());
+ return this;
+ }
+}
diff --git a/src/org/virion/jam/table/HeaderRenderer.java b/src/org/virion/jam/table/HeaderRenderer.java
new file mode 100644
index 0000000..c8a3b4f
--- /dev/null
+++ b/src/org/virion/jam/table/HeaderRenderer.java
@@ -0,0 +1,52 @@
+package org.virion.jam.table;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import java.awt.*;
+
+public class HeaderRenderer extends DefaultTableCellRenderer {
+
+ public HeaderRenderer(int alignment, Insets insets) {
+ setHorizontalAlignment(alignment);
+ setOpaque(true);
+
+ // This call is needed because DefaultTableCellRenderer calls setBorder()
+ // in its constructor, which is executed after updateUI()
+ javax.swing.border.Border border = UIManager.getBorder("TableHeader.cellBorder");
+ setBorder(new CompoundBorder(border, new EmptyBorder(insets)));
+ }
+
+ public void updateUI() {
+ super.updateUI();
+ setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean selected, boolean focused, int row, int column) {
+ JTableHeader header;
+
+ if (table != null && (header = table.getTableHeader()) != null) {
+ setEnabled(header.isEnabled());
+ setComponentOrientation(header.getComponentOrientation());
+
+ setForeground(header.getForeground());
+ setBackground(header.getBackground());
+ setFont(header.getFont());
+ } else {
+ /* Use sensible values instead of random leftover values from the last call */
+ setEnabled(true);
+ setComponentOrientation(ComponentOrientation.UNKNOWN);
+
+ setForeground(UIManager.getColor("TableHeader.foreground"));
+ setBackground(UIManager.getColor("TableHeader.background"));
+ setFont(UIManager.getFont("TableHeader.font"));
+ }
+
+ setValue(value);
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/table/RealNumberCellEditor.java b/src/org/virion/jam/table/RealNumberCellEditor.java
new file mode 100644
index 0000000..6d81014
--- /dev/null
+++ b/src/org/virion/jam/table/RealNumberCellEditor.java
@@ -0,0 +1,46 @@
+package org.virion.jam.table;
+
+import org.virion.jam.components.RealNumberField;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+
+public class RealNumberCellEditor extends DefaultCellEditor {
+
+ private RealNumberField editor;
+
+ public RealNumberCellEditor(double minValue, double maxValue) {
+ super(new RealNumberField(minValue, maxValue));
+
+ editor = (RealNumberField) getComponent();
+
+ setClickCountToStart(2); //This is usually 1 or 2.
+
+ // Must do this so that editing stops when appropriate.
+ editor.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fireEditingStopped();
+ }
+ });
+ }
+
+ protected void fireEditingStopped() {
+ super.fireEditingStopped();
+ }
+
+ public Object getCellEditorValue() {
+ return editor.getValue();
+ }
+
+ public Component getTableCellEditorComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column) {
+ editor.setValue(((Double) value).doubleValue());
+ return editor;
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/table/TableRenderer.java b/src/org/virion/jam/table/TableRenderer.java
new file mode 100644
index 0000000..4ab5db8
--- /dev/null
+++ b/src/org/virion/jam/table/TableRenderer.java
@@ -0,0 +1,57 @@
+package org.virion.jam.table;
+
+import javax.swing.*;
+import javax.swing.plaf.BorderUIResource;
+import javax.swing.table.DefaultTableCellRenderer;
+import java.awt.*;
+
+
+public class TableRenderer extends DefaultTableCellRenderer {
+ protected Color bg1 = new Color(0xED, 0xF3, 0xFE);
+ protected Color bg2 = Color.white;
+ protected boolean striped;
+
+ public TableRenderer(int alignment, Insets insets) {
+
+ this(true, alignment, insets);
+ }
+
+ public TableRenderer(boolean striped, int alignment, Insets insets) {
+ super();
+ this.striped = striped;
+ setOpaque(true);
+ setHorizontalAlignment(alignment);
+ if (insets != null) {
+ setBorder(new BorderUIResource.EmptyBorderUIResource(insets));
+ }
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+
+ if (value != null) {
+ setText(value.toString());
+ }
+ setEnabled(table.isEnabled());
+ setFont(table.getFont());
+
+ // if cell is selected, set background color to default cell selection background color
+ if (isSelected) {
+ setBackground(table.getSelectionBackground());
+ setForeground(table.getSelectionForeground());
+ } else {
+ if (striped) {
+ if (row % 2 == 0) {
+ setBackground(bg1);
+ } else {
+ setBackground(bg2);
+ }
+ } else {
+ setBackground(table.getBackground());
+ }
+ setForeground(table.getForeground());
+ }
+
+ return this;
+ }
+}
diff --git a/src/org/virion/jam/table/WholeNumberCellEditor.java b/src/org/virion/jam/table/WholeNumberCellEditor.java
new file mode 100644
index 0000000..ce5af0b
--- /dev/null
+++ b/src/org/virion/jam/table/WholeNumberCellEditor.java
@@ -0,0 +1,46 @@
+package org.virion.jam.table;
+
+import org.virion.jam.components.WholeNumberField;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+
+public class WholeNumberCellEditor extends DefaultCellEditor {
+
+ private WholeNumberField editor;
+
+ public WholeNumberCellEditor(int minValue, int maxValue) {
+ super(new WholeNumberField(minValue, maxValue));
+
+ editor = (WholeNumberField) getComponent();
+
+ setClickCountToStart(2); //This is usually 1 or 2.
+
+ // Must do this so that editing stops when appropriate.
+ editor.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fireEditingStopped();
+ }
+ });
+ }
+
+ protected void fireEditingStopped() {
+ super.fireEditingStopped();
+ }
+
+ public Object getCellEditorValue() {
+ return editor.getValue();
+ }
+
+ public Component getTableCellEditorComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column) {
+ editor.setValue(((Integer) value).intValue());
+ return editor;
+ }
+}
\ No newline at end of file
diff --git a/src/org/virion/jam/toolbar/GenericToolbarItem.java b/src/org/virion/jam/toolbar/GenericToolbarItem.java
new file mode 100644
index 0000000..8524217
--- /dev/null
+++ b/src/org/virion/jam/toolbar/GenericToolbarItem.java
@@ -0,0 +1,40 @@
+package org.virion.jam.toolbar;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author rambaut
+ * Date: Oct 18, 2005
+ * Time: 10:09:21 PM
+ */
+public class GenericToolbarItem extends JPanel implements ToolbarItem {
+
+ public GenericToolbarItem(String title, String toolTipText, JComponent component) {
+ setLayout(new BorderLayout());
+ add(component, BorderLayout.NORTH);
+
+ label = new JLabel(title);
+ label.setHorizontalAlignment(SwingConstants.CENTER);
+ add(label, BorderLayout.SOUTH);
+ setToolTipText(toolTipText);
+ }
+
+ public void setToolbarOptions(ToolbarOptions options) {
+ switch (options.getDisplay()) {
+ case ToolbarOptions.ICON_AND_TEXT:
+ case ToolbarOptions.TEXT_ONLY:
+ label.setVisible(true);
+ break;
+ case ToolbarOptions.ICON_ONLY:
+ label.setVisible(false);
+ break;
+ }
+ }
+
+ public void setAction(Action action) {
+ throw new UnsupportedOperationException("Method setAction() not supported in GenericToolBarItem");
+ }
+
+ private JLabel label;
+}
diff --git a/src/org/virion/jam/toolbar/Toolbar.java b/src/org/virion/jam/toolbar/Toolbar.java
new file mode 100644
index 0000000..0dd646f
--- /dev/null
+++ b/src/org/virion/jam/toolbar/Toolbar.java
@@ -0,0 +1,167 @@
+package org.virion.jam.toolbar;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.event.*;
+import java.awt.*;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+/**
+ * @author Andrew Rambaut
+ * @author Alexei Drummond
+ *
+ * @version $Id: Toolbar.java 488 2006-10-25 23:09:21Z rambaut $
+ */
+public class Toolbar extends JToolBar {
+
+ public Toolbar() {
+ this(new ToolbarOptions(ToolbarOptions.ICON_AND_TEXT, false));
+ }
+
+ public Toolbar(ToolbarOptions options) {
+
+ this.options = options;
+
+ // This property is only used if the Quaqua library is loaded on
+ // Mac OS X - it makes toolbars look more Mac-like
+ putClientProperty("Quaqua.ToolBar.isDividerDrawn", Boolean.TRUE);
+ putClientProperty("JToolBar.isRollover", Boolean.TRUE);
+
+ setLayout(new GridBagLayout());
+
+// setBorder(BorderFactory.createCompoundBorder(
+// BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray),
+// BorderFactory.createEmptyBorder(0, 12, 0, 12)));
+
+ if (options != null) {
+ final JPopupMenu menu = new JPopupMenu();
+
+ menu.setLightWeightPopupEnabled(false);
+
+ ChangeListener listener = new ChangeListener() {
+ public void stateChanged(ChangeEvent changeEvent) {
+ toolbarOptionsChanged();
+ }
+ };
+
+ ButtonGroup group = new ButtonGroup();
+ iconTextMenuItem = new JRadioButtonMenuItem("Icon & Text");
+ iconTextMenuItem.addChangeListener(listener);
+ group.add(iconTextMenuItem);
+ menu.add(iconTextMenuItem);
+
+ iconOnlyMenuItem = new JRadioButtonMenuItem("Icon Only");
+ iconOnlyMenuItem.addChangeListener(listener);
+ group.add(iconOnlyMenuItem);
+ menu.add(iconOnlyMenuItem);
+
+ textOnlyMenuItem = new JRadioButtonMenuItem("Text Only");
+ textOnlyMenuItem.addChangeListener(listener);
+ group.add(textOnlyMenuItem);
+ menu.add(textOnlyMenuItem);
+
+ menu.add(new JSeparator());
+
+ smallSizeMenuItem = new JCheckBoxMenuItem("Small Size");
+ smallSizeMenuItem.addChangeListener(listener);
+ menu.add(smallSizeMenuItem);
+
+ menu.add(new JSeparator());
+
+ JMenuItem item = new JMenuItem("Customize Toolbar...");
+ item.setEnabled(false);
+ menu.add(item);
+
+ // Set the component to show the popup menu
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent evt) {
+ if (evt.isPopupTrigger()) {
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ }
+ }
+ public void mouseReleased(MouseEvent evt) {
+ if (evt.isPopupTrigger()) {
+ menu.show(evt.getComponent(), evt.getX(), evt.getY());
+ }
+ }
+ });
+
+ iconTextMenuItem.setSelected(options.getDisplay() == ToolbarOptions.ICON_AND_TEXT);
+ iconOnlyMenuItem.setSelected(options.getDisplay() == ToolbarOptions.ICON_ONLY);
+ textOnlyMenuItem.setSelected(options.getDisplay() == ToolbarOptions.TEXT_ONLY);
+ smallSizeMenuItem.setSelected(options.getSmallSize());
+ } else {
+ iconTextMenuItem = null;
+ iconOnlyMenuItem = null;
+ textOnlyMenuItem = null;
+ smallSizeMenuItem = null;
+ }
+ }
+
+ public void addComponent(JComponent component) {
+ if (component instanceof ToolbarItem) {
+ ToolbarItem item = (ToolbarItem)component;
+ toolbarItems.add(item);
+ item.setToolbarOptions(options);
+ }
+ addItem(component);
+ }
+
+ public void addSeperator() {
+ addItem(new Separator());
+ }
+
+ public void addSpace() {
+ addItem(new Separator());
+ }
+
+ private void addItem(JComponent item) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = GridBagConstraints.RELATIVE;
+ c.gridy = 0;
+ c.weightx = 0;
+ add(item, c);
+ }
+
+ public void addFlexibleSpace() {
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = GridBagConstraints.RELATIVE;
+ c.gridy = 0;
+ c.weightx = 1;
+ add(new Separator(), c);
+ }
+
+ private void toolbarOptionsChanged() {
+ int display = (iconTextMenuItem.isSelected() ? ToolbarOptions.ICON_AND_TEXT :
+ (iconOnlyMenuItem.isSelected() ? ToolbarOptions.ICON_ONLY :
+ ToolbarOptions.TEXT_ONLY));
+ boolean smallSize = smallSizeMenuItem.isSelected();
+
+ setToolbarOptions(new ToolbarOptions(display, smallSize));
+ }
+
+ private void setToolbarOptions(ToolbarOptions toolbarOptions) {
+ this.options = toolbarOptions;
+
+ Iterator<ToolbarItem> iter = toolbarItems.iterator();
+ while (iter.hasNext()) {
+ ToolbarItem item = iter.next();
+ item.setToolbarOptions(options);
+ }
+
+ validate();
+ repaint();
+ }
+
+ private ToolbarOptions options;
+
+ private final JRadioButtonMenuItem iconTextMenuItem;
+ private final JRadioButtonMenuItem iconOnlyMenuItem;
+ private final JRadioButtonMenuItem textOnlyMenuItem;
+ private final JCheckBoxMenuItem smallSizeMenuItem;
+
+ private List<ToolbarItem> toolbarItems = new ArrayList<ToolbarItem>();
+}
diff --git a/src/org/virion/jam/toolbar/ToolbarAction.java b/src/org/virion/jam/toolbar/ToolbarAction.java
new file mode 100644
index 0000000..c85d356
--- /dev/null
+++ b/src/org/virion/jam/toolbar/ToolbarAction.java
@@ -0,0 +1,51 @@
+package org.virion.jam.toolbar;
+
+import javax.swing.*;
+
+/**
+ * @author rambaut
+ * Date: Oct 18, 2005
+ * Time: 10:10:52 PM
+ */
+public abstract class ToolbarAction extends AbstractAction {
+
+ protected ToolbarAction(String label, String toolTipText, Icon icon) {
+ this(label, toolTipText, icon, null, null);
+ }
+
+ protected ToolbarAction(String label, String toolTipText, Icon icon, Icon disabledIcon, Icon pressedIcon) {
+ super(label, icon);
+
+ this.label = label;
+ this.toolTipText = toolTipText;
+ this.icon = icon;
+ this.disabledIcon = disabledIcon;
+ this.pressedIcon = pressedIcon;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public Icon getIcon() {
+ return icon;
+ }
+
+ public Icon getDisabledIcon() {
+ return disabledIcon;
+ }
+
+ public Icon getPressedIcon() {
+ return pressedIcon;
+ }
+
+ public String getToolTipText() {
+ return toolTipText;
+ }
+
+ private String label;
+ private String toolTipText;
+ private Icon icon;
+ private Icon disabledIcon;
+ private Icon pressedIcon;
+}
diff --git a/src/org/virion/jam/toolbar/ToolbarButton.java b/src/org/virion/jam/toolbar/ToolbarButton.java
new file mode 100644
index 0000000..5947bf9
--- /dev/null
+++ b/src/org/virion/jam/toolbar/ToolbarButton.java
@@ -0,0 +1,57 @@
+package org.virion.jam.toolbar;
+
+import javax.swing.*;
+
+/**
+ * @author rambaut
+ * Date: Oct 18, 2005
+ * Time: 10:09:21 PM
+ */
+public class ToolbarButton extends JButton implements ToolbarItem {
+
+ public ToolbarButton(ToolbarAction action) {
+ super(action);
+
+ setHorizontalTextPosition(SwingConstants.CENTER);
+ setVerticalTextPosition(SwingConstants.BOTTOM);
+ putClientProperty("JButton.buttonType", "toolbar");
+ setBorderPainted(false);
+
+ setToolTipText(action.getToolTipText());
+
+ setDisabledIcon(action.getDisabledIcon());
+ setPressedIcon(action.getPressedIcon());
+ }
+
+ public void setToolbarOptions(ToolbarOptions options) {
+ switch (options.getDisplay()) {
+ case ToolbarOptions.ICON_AND_TEXT:
+ setText(action.getLabel());
+ setIcon(action.getIcon());
+ setDisabledIcon(action.getDisabledIcon());
+ setPressedIcon(action.getPressedIcon());
+ break;
+ case ToolbarOptions.ICON_ONLY:
+ setText(null);
+ setIcon(action.getIcon());
+ setDisabledIcon(action.getDisabledIcon());
+ setPressedIcon(action.getPressedIcon());
+ break;
+ case ToolbarOptions.TEXT_ONLY:
+ setText(action.getLabel());
+ setIcon(null);
+ setDisabledIcon(null);
+ setPressedIcon(null);
+ break;
+ }
+ }
+
+ public void setAction(Action action) {
+ super.setAction(action);
+ if (action instanceof ToolbarAction) {
+ this.action = (ToolbarAction)action;
+ }
+ }
+
+ private ToolbarAction action;
+}
diff --git a/src/org/virion/jam/toolbar/ToolbarItem.java b/src/org/virion/jam/toolbar/ToolbarItem.java
new file mode 100644
index 0000000..29bda43
--- /dev/null
+++ b/src/org/virion/jam/toolbar/ToolbarItem.java
@@ -0,0 +1,10 @@
+package org.virion.jam.toolbar;
+
+/**
+ * @author rambaut
+ * Date: Oct 18, 2005
+ * Time: 10:19:19 PM
+ */
+public interface ToolbarItem {
+ void setToolbarOptions(ToolbarOptions options);
+}
diff --git a/src/org/virion/jam/toolbar/ToolbarOptions.java b/src/org/virion/jam/toolbar/ToolbarOptions.java
new file mode 100644
index 0000000..5f00850
--- /dev/null
+++ b/src/org/virion/jam/toolbar/ToolbarOptions.java
@@ -0,0 +1,29 @@
+package org.virion.jam.toolbar;
+
+/**
+ * @author rambaut
+ * Date: Oct 18, 2005
+ * Time: 10:23:01 PM
+ */
+public final class ToolbarOptions {
+
+ public static final int ICON_AND_TEXT = 0;
+ public static final int ICON_ONLY = 1;
+ public static final int TEXT_ONLY = 2;
+
+ public ToolbarOptions(int display, boolean smallSize) {
+ this.display = display;
+ this.smallSize = smallSize;
+ }
+
+ public int getDisplay() {
+ return display;
+ }
+
+ public boolean getSmallSize() {
+ return smallSize;
+ }
+
+ private int display = ICON_AND_TEXT;
+ private boolean smallSize = false;
+}
diff --git a/src/org/virion/jam/util/BrowserLauncher.java b/src/org/virion/jam/util/BrowserLauncher.java
new file mode 100644
index 0000000..bd03040
--- /dev/null
+++ b/src/org/virion/jam/util/BrowserLauncher.java
@@ -0,0 +1,617 @@
+package org.virion.jam.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * BrowserLauncher is a class that provides one static method, openURL, which opens the default
+ * web browser for the current user of the system to the given URL. It may support other
+ * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously
+ * tested and is not guaranteed to work.
+ * <p>
+ * Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms
+ * that are not part of the standard JDK. What we're trying to do, though, is to take something
+ * that's frequently desirable but inherently platform-specific -- opening a default browser --
+ * and allow programmers (you, for example) to do so without worrying about dropping into native
+ * code or doing anything else similarly evil.
+ * <p>
+ * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without
+ * modification or a need for additional libraries. All classes that are required on certain
+ * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not
+ * found, will not cause this to do anything other than returning an error when opening the
+ * browser.
+ * <p>
+ * There are certain system requirements for this class, as it's running through Runtime.exec(),
+ * which is Java's way of making a native system call. Currently, this requires that a Macintosh
+ * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that
+ * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder
+ * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and
+ * 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems
+ * (Windows 95, 98, and NT 4.0, as well as later versions of all). On other systems, this drops
+ * back from the inherently platform-sensitive concept of a default browser and simply attempts
+ * to launch Netscape via a shell command.
+ * <p>
+ * This code is Copyright 1999-2001 by Eric Albert (ejalbert at cs.stanford.edu) and may be
+ * redistributed or modified in any form without restrictions as long as the portion of this
+ * comment from this paragraph through the end of the comment is not removed. The author
+ * requests that he be notified of any application, applet, or other binary that makes use of
+ * this code, but that's more out of curiosity than anything and is not required. This software
+ * includes no warranty. The author is not repsonsible for any loss of data or functionality
+ * or any adverse or unexpected effects of using this software.
+ * <p>
+ * Credits:
+// * <br>Steven Spencer, JavaWorld magazine (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip 66</a>)
+ * <br>Thanks also to Ron B. Yeh, Eric Shmacapiro, Ben Engber, Paul Teitlebaum, Andrea Cantatore,
+ * Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
+ *
+ * @author Eric Albert (<a href="mailto:ejalbert at cs.stanford.edu">ejalbert at cs.stanford.edu</a>)
+ * @version 1.4b1 (Released June 20, 2001)
+ */
+public class BrowserLauncher {
+
+ /**
+ * The Java virtual machine that we are running on. Actually, in most cases we only care
+ * about the operating system, but some operating systems require us to switch on the VM. */
+ private static int jvm;
+
+ /** The browser for the system */
+ private static Object browser;
+
+ /**
+ * Caches whether any classes, methods, and fields that are not part of the JDK and need to
+ * be dynamically loaded at runtime loaded successfully.
+ * <p>
+ * Note that if this is <code>false</code>, <code>openURL()</code> will always return an
+ * IOException.
+ */
+ private static boolean loadedWithoutErrors;
+
+ /** The com.apple.mrj.MRJFileUtils class */
+ private static Class mrjFileUtilsClass;
+
+ /** The com.apple.mrj.MRJOSType class */
+ private static Class mrjOSTypeClass;
+
+ /** The com.apple.MacOS.AEDesc class */
+ private static Class aeDescClass;
+
+ /** The <init>(int) method of com.apple.MacOS.AETarget */
+ private static Constructor aeTargetConstructor;
+
+ /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
+ private static Constructor appleEventConstructor;
+
+ /** The <init>(String) method of com.apple.MacOS.AEDesc */
+ private static Constructor aeDescConstructor;
+
+ /** The findFolder method of com.apple.mrj.MRJFileUtils */
+ private static Method findFolder;
+
+ /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
+ private static Method getFileCreator;
+
+ /** The getFileType method of com.apple.mrj.MRJFileUtils */
+ private static Method getFileType;
+
+ /** The openURL method of com.apple.mrj.MRJFileUtils */
+ private static Method openURL;
+
+ /** The makeOSType method of com.apple.MacOS.OSUtils */
+ private static Method makeOSType;
+
+ /** The putParameter method of com.apple.MacOS.AppleEvent */
+ private static Method putParameter;
+
+ /** The sendNoReply method of com.apple.MacOS.AppleEvent */
+ private static Method sendNoReply;
+
+ /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
+ private static Object kSystemFolderType;
+
+ /** The keyDirectObject AppleEvent parameter type */
+ private static Integer keyDirectObject;
+
+ /** The kAutoGenerateReturnID AppleEvent code */
+ private static Integer kAutoGenerateReturnID;
+
+ /** The kAnyTransactionID AppleEvent code */
+ private static Integer kAnyTransactionID;
+
+ /** The linkage object required for JDirect 3 on Mac OS X. */
+ private static Object linkage;
+
+ /** The framework to reference on Mac OS X */
+ private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
+
+ /** JVM constant for MRJ 2.0 */
+ private static final int MRJ_2_0 = 0;
+
+ /** JVM constant for MRJ 2.1 or later */
+ private static final int MRJ_2_1 = 1;
+
+ /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
+ private static final int MRJ_3_0 = 3;
+
+ /** JVM constant for MRJ 3.1 */
+ private static final int MRJ_3_1 = 4;
+
+ /** JVM constant for any Windows NT JVM */
+ private static final int WINDOWS_NT = 5;
+
+ /** JVM constant for any Windows 9x JVM */
+ private static final int WINDOWS_9x = 6;
+
+ /** JVM constant for any other platform */
+ private static final int OTHER = -1;
+
+ /**
+ * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep non-U.S. English
+ * systems from working properly.
+ */
+ private static final String FINDER_TYPE = "FNDR";
+
+ /**
+ * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the
+ * application.
+ */
+ private static final String FINDER_CREATOR = "MACS";
+
+ /** The name for the AppleEvent type corresponding to a GetURL event. */
+ private static final String GURL_EVENT = "GURL";
+
+ /**
+ * The first parameter that needs to be passed into Runtime.exec() to open the default web
+ * browser on Windows.
+ */
+ private static final String FIRST_WINDOWS_PARAMETER = "/c";
+
+ /** The second parameter for Runtime.exec() on Windows. */
+ private static final String SECOND_WINDOWS_PARAMETER = "start";
+
+ /**
+ * The third parameter for Runtime.exec() on Windows. This is a "title"
+ * parameter that the command line expects. Setting this parameter allows
+ * URLs containing spaces to work.
+ */
+ private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
+
+ /**
+ * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape
+ * on many command-line systems.
+ */
+ private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
+ private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
+ private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
+
+ /**
+ * The message from any exception thrown throughout the initialization process.
+ */
+ private static String errorMessage;
+
+ /**
+ * An initialization block that determines the operating system and loads the necessary
+ * runtime data.
+ */
+ static {
+ loadedWithoutErrors = true;
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Mac OS")) {
+ String mrjVersion = System.getProperty("mrj.version");
+ String majorMRJVersion = mrjVersion.substring(0, 3);
+ try {
+ double version = Double.valueOf(majorMRJVersion).doubleValue();
+ if (version == 2) {
+ jvm = MRJ_2_0;
+ } else if (version >= 2.1 && version < 3) {
+ // Assume that all 2.x versions of MRJ work the same. MRJ 2.1 actually
+ // works via Runtime.exec() and 2.2 supports that but has an openURL() method
+ // as well that we currently ignore.
+ jvm = MRJ_2_1;
+ } else if (version == 3.0) {
+ jvm = MRJ_3_0;
+ } else if (version >= 3.1) {
+ // Assume that all 3.1 and later versions of MRJ work the same.
+ jvm = MRJ_3_1;
+ } else {
+ loadedWithoutErrors = false;
+ errorMessage = "Unsupported MRJ version: " + version;
+ }
+ } catch (NumberFormatException nfe) {
+ loadedWithoutErrors = false;
+ errorMessage = "Invalid MRJ version: " + mrjVersion;
+ }
+ } else if (osName.startsWith("Windows")) {
+ if (osName.indexOf("9") != -1) {
+ jvm = WINDOWS_9x;
+ } else {
+ jvm = WINDOWS_NT;
+ }
+ } else {
+ jvm = OTHER;
+ }
+
+ if (loadedWithoutErrors) { // if we haven't hit any errors yet
+ loadedWithoutErrors = loadClasses();
+ }
+ }
+
+ /**
+ * This class should be never be instantiated; this just ensures so.
+ */
+ private BrowserLauncher() { }
+
+ /**
+ * Called by a static initializer to load any classes, fields, and methods required at runtime
+ * to locate the user's web browser.
+ * @return <code>true</code> if all intialization succeeded
+ * <code>false</code> if any portion of the initialization failed
+ */
+ private static boolean loadClasses() {
+ switch (jvm) {
+ case MRJ_2_0:
+ try {
+ Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
+ Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
+ Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
+ Class aeClass = Class.forName("com.apple.MacOS.ae");
+ aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
+
+ aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class });
+ appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class });
+ aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
+
+ makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class });
+ putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
+ sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { });
+
+ Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
+ keyDirectObject = (Integer) keyDirectObjectField.get(null);
+ Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
+ kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
+ Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
+ kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (NoSuchFieldException nsfe) {
+ errorMessage = nsfe.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_2_1:
+ try {
+ mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
+ mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
+ Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
+ kSystemFolderType = systemFolderField.get(null);
+ findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
+ getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class });
+ getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchFieldException nsfe) {
+ errorMessage = nsfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (SecurityException se) {
+ errorMessage = se.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_3_0:
+ try {
+ Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
+ Constructor constructor = linker.getConstructor(new Class[]{ Class.class });
+ linkage = constructor.newInstance(new Object[] { BrowserLauncher.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (InvocationTargetException ite) {
+ errorMessage = ite.getMessage();
+ return false;
+ } catch (InstantiationException ie) {
+ errorMessage = ie.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_3_1:
+ try {
+ mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
+ openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Attempts to locate the default web browser on the local system. Caches global so it
+ * only locates the browser once for each use of this class per JVM instance.
+ * @return The browser for the system. Note that this may not be what you would consider
+ * to be a standard web browser; instead, it's the application that gets called to
+ * open the default web browser. In some cases, this will be a non-String object
+ * that provides the means of calling the default browser.
+ */
+ private static Object locateBrowser() {
+ if (browser != null) {
+ return browser;
+ }
+ switch (jvm) {
+ case MRJ_2_0:
+ try {
+ Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
+ Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
+ Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
+ Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID });
+ // Don't set browser = appleEvent because then the next time we call
+ // locateBrowser(), we'll get the same AppleEvent, to which we'll already have
+ // added the relevant parameter. Instead, regenerate the AppleEvent every time.
+ // There's probably a way to do this better; if any has any ideas, please let
+ // me know.
+ return appleEvent;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InstantiationException ie) {
+ browser = null;
+ errorMessage = ie.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getMessage();
+ return browser;
+ }
+ case MRJ_2_1:
+ File systemFolder;
+ try {
+ systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
+ } catch (IllegalArgumentException iare) {
+ browser = null;
+ errorMessage = iare.getMessage();
+ return browser;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
+ return browser;
+ }
+ String[] systemFolderFiles = systemFolder.list();
+ // Avoid a FilenameFilter because that can't be stopped mid-list
+ for(int i = 0; i < systemFolderFiles.length; i++) {
+ try {
+ File file = new File(systemFolder, systemFolderFiles[i]);
+ if (!file.isFile()) {
+ continue;
+ }
+ // We're looking for a file with a creator code of 'MACS' and
+ // a type of 'FNDR'. Only requiring the type global in non-Finder
+ // applications being picked up on certain Mac OS 9 systems,
+ // especially German ones, and sending a GURL event to those
+ // applications global in a logout under Multiple Users.
+ Object fileType = getFileType.invoke(null, new Object[] { file });
+ if (FINDER_TYPE.equals(fileType.toString())) {
+ Object fileCreator = getFileCreator.invoke(null, new Object[] { file });
+ if (FINDER_CREATOR.equals(fileCreator.toString())) {
+ browser = file.toString(); // Actually the Finder, but that's OK
+ return browser;
+ }
+ }
+ } catch (IllegalArgumentException iare) {
+ browser = browser;
+ errorMessage = iare.getMessage();
+ return null;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
+ return browser;
+ }
+ }
+ browser = null;
+ break;
+ case MRJ_3_0:
+ case MRJ_3_1:
+ browser = ""; // Return something non-null
+ break;
+ case WINDOWS_NT:
+ browser = "cmd.exe";
+ break;
+ case WINDOWS_9x:
+ browser = "command.com";
+ break;
+ case OTHER:
+ default:
+ browser = Utils.getEnv("BROWSER");
+ if( browser == null ) {
+ browser = getLinuxBrowser();
+ }
+ break;
+ }
+ return browser;
+ }
+
+ /**
+ * Call this if "Linux".equals(System.getProperty("os.name"));
+ *
+ * @return a fully qualified browser executable, or null if none is found.
+ */
+ private static String getLinuxBrowser() {
+
+ final String[] browsers = { "opera", "firefox", "mozilla", "galeon", "konqueror", "netscape" };
+ final String[] cmd = { "which", null };
+ String result = null;
+
+ try {
+ for (int i = 0; i < browsers.length; i++) {
+ cmd[1] = browsers[i];
+ Process process = Runtime.getRuntime().exec(cmd);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ result = reader.readLine();
+ reader.close();
+ if (result != null) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ // This means we get someting back even if the OS isn't Linux
+ result = "netscape";
+ }
+ return result;
+ }
+
+ /**
+ * Attempts to open the default web browser to the given URL.
+ * @param url The URL to open
+ * @throws IOException If the web browser could not be located or does not run
+ */
+ public static void openURL(String url) throws IOException {
+ if (!loadedWithoutErrors) {
+ throw new IOException("Exception in finding browser: " + errorMessage);
+ }
+ Object browser = locateBrowser();
+ if (browser == null) {
+ throw new IOException("Unable to locate browser: " + errorMessage);
+ }
+
+ switch (jvm) {
+ case MRJ_2_0:
+ Object aeDesc = null;
+ try {
+ aeDesc = aeDescConstructor.newInstance(new Object[] { url });
+ putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
+ sendNoReply.invoke(browser, new Object[] { });
+ } catch (InvocationTargetException ite) {
+ throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage());
+ } catch (InstantiationException ie) {
+ throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
+ } finally {
+ aeDesc = null; // Encourage it to get disposed if it was created
+ browser = null; // Ditto
+ }
+ break;
+ case MRJ_2_1:
+ Runtime.getRuntime().exec(new String[] { (String) browser, url } );
+ break;
+ case MRJ_3_0:
+ int[] instance = new int[1];
+ int result = ICStart(instance, 0);
+ if (result == 0) {
+ int[] selectionStart = new int[] { 0 };
+ byte[] urlBytes = url.getBytes();
+ int[] selectionEnd = new int[] { urlBytes.length };
+ result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
+ urlBytes.length, selectionStart,
+ selectionEnd);
+ if (result == 0) {
+ // Ignore the return value; the URL was launched successfully
+ // regardless of what happens here.
+ ICStop(instance);
+ } else {
+ throw new IOException("Unable to launch URL: " + result);
+ }
+ } else {
+ throw new IOException("Unable to create an Internet Config instance: " + result);
+ }
+ break;
+ case MRJ_3_1:
+ try {
+ openURL.invoke(null, new Object[] { url });
+ } catch (InvocationTargetException ite) {
+ throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage());
+ }
+ break;
+ case WINDOWS_NT:
+ case WINDOWS_9x:
+ // Add quotes around the URL to allow ampersands and other special
+ // characters to work.
+ Process process = Runtime.getRuntime().exec(new String[] { (String) browser,
+ FIRST_WINDOWS_PARAMETER,
+ SECOND_WINDOWS_PARAMETER,
+ THIRD_WINDOWS_PARAMETER,
+ '"' + url + '"' });
+ // This avoids a memory leak on some versions of Java on Windows.
+ // That's hinted at in <http://developer.java.sun.com/developer/qow/archive/68/>.
+ try {
+ process.waitFor();
+ process.exitValue();
+ } catch (InterruptedException ie) {
+ throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
+ }
+ break;
+ case OTHER:
+ // Assume that we're on Unix and that Netscape is installed
+
+ // First, attempt to open the URL in a currently running session of Netscape
+ process = Runtime.getRuntime().exec(new String[] { (String) browser,
+ NETSCAPE_REMOTE_PARAMETER,
+ NETSCAPE_OPEN_PARAMETER_START +
+ url +
+ NETSCAPE_OPEN_PARAMETER_END });
+ try {
+ int exitCode = process.waitFor();
+ if (exitCode != 0) { // if Netscape was not open
+ Runtime.getRuntime().exec(new String[] { (String) browser, url });
+ }
+ } catch (InterruptedException ie) {
+ throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
+ }
+ break;
+ default:
+ // This should never occur, but if it does, we'll try the simplest thing possible
+ Runtime.getRuntime().exec(new String[] { (String) browser, url });
+ break;
+ }
+ }
+
+ /**
+ * Methods required for Mac OS X. The presence of native methods does not cause
+ * any problems on other platforms.
+ */
+ private native static int ICStart(int[] instance, int signature);
+ private native static int ICStop(int[] instance);
+ private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len,
+ int[] selectionStart, int[] selectionEnd);
+}
diff --git a/src/org/virion/jam/util/IconUtils.java b/src/org/virion/jam/util/IconUtils.java
new file mode 100644
index 0000000..65f1005
--- /dev/null
+++ b/src/org/virion/jam/util/IconUtils.java
@@ -0,0 +1,169 @@
+package org.virion.jam.util;
+
+import javax.swing.*;
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.*;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * @author rambaut
+ * Date: Jul 29, 2004
+ * Time: 10:12:49 AM
+ */
+public class IconUtils {
+ /**
+ * @return a named image from file or resource bundle.
+ */
+ public static Image getImage(String name) {
+ return getImage(IconUtils.class, name);
+ }
+
+ private static void showWarning(final String message){
+
+ if (EventQueue.isDispatchThread()){
+ JOptionPane.showMessageDialog(null, message, "Warning",JOptionPane.WARNING_MESSAGE);
+ }
+ else {
+ // must show message dialog in the event dispatch thread, otherwise can crash
+ try {
+ EventQueue.invokeAndWait(new Runnable() {
+ public void run() {
+ JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
+
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ /**
+ * @return a named image from file or resource bundle.
+ */
+ public static Image getImage(Class resourceClass, String name) {
+ java.net.URL url = resourceClass.getResource(name);
+ if (url != null) {
+ return Toolkit.getDefaultToolkit().createImage(url);
+ } else {
+ showWarning( "Image " + name + " could not be loaded.");
+ return null;
+ }
+ }
+
+ /**
+ * @return a named image from file or resource bundle.
+ */
+ public static BufferedImage getBufferedImage(String name) {
+ return getBufferedImage(IconUtils.class, name);
+ }
+
+ /**
+ * @return a named image from file or resource bundle.
+ */
+ public static BufferedImage getBufferedImage(Class resourceClass, String name) {
+
+ java.net.URL url = resourceClass.getResource(name);
+ if (url != null) {
+ try {
+ return ImageIO.read(url);
+ } catch (IOException ioe) {
+ showWarning("Image " + name + " could not be loaded.");
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return a named icon from file or resource bundle.
+ */
+ public static Icon getIcon(String name) {
+ return getIcon(IconUtils.class, name);
+ }
+
+ /**
+ * @return a named icon from file or resource bundle.
+ */
+ public static Icon getIcon(Class resourceClass, String name) {
+ Image image = getImage(resourceClass, name);
+ if (image != null) {
+ return new ImageIcon(image);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a slightly brighter version of the icon.
+ */
+ public static Icon brighten(Icon icon) {
+ BufferedImage img = getBufferedImageFromIcon(icon);
+ if(img == null)
+ return icon;
+ BufferedImageOp op = new RescaleOp(1.25f, 0, null);
+ return new ImageIcon(op.filter(img, null));
+ }
+
+ /**
+ * Returns a slightly darker version of the icon.
+ */
+ public static Icon darken(Icon icon) {
+ BufferedImage img = getBufferedImageFromIcon(icon);
+ if(img == null)
+ return icon;
+ BufferedImageOp op = new RescaleOp(0.75f, 0, null);
+ return new ImageIcon(op.filter(img, null));
+ }
+
+ /**
+ * Returns a grayed version of the icon.
+ */
+ public static Icon gray(Icon icon) {
+ BufferedImage img = getBufferedImageFromIcon(icon);
+ if(img == null)
+ return icon;
+ return new ImageIcon(GrayFilter.createDisabledImage(img));
+ }
+
+ /**
+ * Resizes an icon.
+ */
+ public static Icon resize(Icon icon, int width, int height) {
+ Image image = getImageFromIcon(icon);
+ if(image == null)
+ return icon;
+ image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+ return new ImageIcon(image);
+ }
+
+ /**
+ * Creates an image from an icon.
+ */
+ public static Image getImageFromIcon(Icon icon) {
+ if(icon instanceof ImageIcon) {
+ return ((ImageIcon)icon).getImage();
+ } else {
+ return getBufferedImageFromIcon(icon);
+ }
+ }
+
+ /**
+ * Creates a buffered image from an icon.
+ */
+ public static BufferedImage getBufferedImageFromIcon(Icon icon) {
+ BufferedImage buffer = new BufferedImage(
+ icon.getIconWidth(), icon.getIconHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = buffer.getGraphics();
+ icon.paintIcon(new JLabel(), g, 0,0);
+ g.dispose();
+ return buffer;
+ }
+}
diff --git a/src/org/virion/jam/util/JExceptionDialog.java b/src/org/virion/jam/util/JExceptionDialog.java
new file mode 100644
index 0000000..4616aaf
--- /dev/null
+++ b/src/org/virion/jam/util/JExceptionDialog.java
@@ -0,0 +1,97 @@
+package org.virion.jam.util;
+
+/**
+ * @author adru001
+ */
+public class JExceptionDialog extends javax.swing.JDialog {
+
+ /**
+ * Creates new form JExceptionDialog
+ */
+ public JExceptionDialog(java.awt.Frame parent, boolean modal, String title, String text) {
+ super(parent, modal);
+ initComponents();
+ pack();
+
+ setTitle(title);
+ setText(text);
+ setVisible(true);
+ }
+
+ public void setText(String text) {
+ exceptionTextArea.setText(text);
+ repaint();
+ }
+
+ /**
+ * This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the FormEditor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+ buttonPanel = new javax.swing.JPanel();
+ okButton = new javax.swing.JButton();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ exceptionTextArea = new javax.swing.JTextArea();
+ addWindowListener(new java.awt.event.WindowAdapter() {
+ public void windowClosing(java.awt.event.WindowEvent evt) {
+ closeDialog(evt);
+ }
+ });
+
+ buttonPanel.setLayout(new java.awt.FlowLayout(2, 5, 5));
+
+ okButton.setText("OK");
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+ buttonPanel.add(okButton);
+
+
+ getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
+
+
+ jScrollPane1.setPreferredSize(new java.awt.Dimension(500, 200));
+
+ exceptionTextArea.setEnabled(false);
+ jScrollPane1.setViewportView(exceptionTextArea);
+
+
+ getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
+
+ }//GEN-END:initComponents
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ doClose();
+ }//GEN-LAST:event_okButtonActionPerformed
+
+ /**
+ * Closes the dialog
+ */
+ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
+ doClose();
+ }//GEN-LAST:event_closeDialog
+
+ private void doClose() {
+ setVisible(false);
+ dispose();
+ }
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ new JExceptionDialog(new javax.swing.JFrame(), true, "Exception", "test").setVisible(true);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel buttonPanel;
+ private javax.swing.JButton okButton;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JTextArea exceptionTextArea;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/virion/jam/util/ListItemView.java b/src/org/virion/jam/util/ListItemView.java
new file mode 100644
index 0000000..7845004
--- /dev/null
+++ b/src/org/virion/jam/util/ListItemView.java
@@ -0,0 +1,18 @@
+/*
+ * ListItemView
+ *
+ * Created on 28 August 2001, 11:33
+ */
+
+package org.virion.jam.util;
+
+
+
+
+/**
+ * @author adru001
+ */
+public interface ListItemView {
+
+ void setItemToView(Object o);
+}
diff --git a/src/org/virion/jam/util/LongTask.java b/src/org/virion/jam/util/LongTask.java
new file mode 100644
index 0000000..1615f81
--- /dev/null
+++ b/src/org/virion/jam/util/LongTask.java
@@ -0,0 +1,73 @@
+package org.virion.jam.util;
+
+public abstract class LongTask {
+
+ private SwingWorker worker = null;
+ Object answer;
+ boolean finished = false;
+
+ /**
+ * Does the actual work and returns some kind of result.
+ */
+ public abstract Object doWork() throws java.lang.Exception;
+
+ /**
+ * Called to start the task.
+ */
+ public final void go() {
+ worker = new SwingWorker() {
+ public Object construct() {
+
+ try {
+ answer = doWork();
+ } catch (java.lang.Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ finished = true;
+ return answer;
+ }
+ };
+ worker.start();
+ }
+
+ public final Object getAnswer() {
+ return answer;
+ }
+
+ /**
+ * Called to find out how much work needs
+ * to be done.
+ */
+ public abstract int getLengthOfTask();
+
+ /**
+ * Called to find out how much has been done.
+ */
+ public abstract int getCurrent();
+
+ /**
+ * Called to stop task.
+ */
+ public void stop() {
+ finished = true;
+ }
+
+ /**
+ * Called to find out if the task has completed.
+ */
+ public boolean done() {
+ return finished;
+ }
+
+ /**
+ * Called to get the current message of the task.
+ */
+ public abstract String getMessage();
+
+ /**
+ * Called to get the description of this task.
+ */
+ public String getDescription() {
+ return "Running a long task...";
+ }
+}
diff --git a/src/org/virion/jam/util/LongTaskMonitor.java b/src/org/virion/jam/util/LongTaskMonitor.java
new file mode 100644
index 0000000..230d110
--- /dev/null
+++ b/src/org/virion/jam/util/LongTaskMonitor.java
@@ -0,0 +1,60 @@
+package org.virion.jam.util;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class LongTaskMonitor {
+
+ public final static int ONE_SECOND = 1000;
+
+ private ProgressMonitor progressMonitor;
+ private Timer timer;
+ private LongTask task;
+ private JComponent parent;
+ private TaskListener listener;
+
+ public LongTaskMonitor(LongTask task, JComponent parent, TaskListener listener) {
+
+ this.task = task;
+ this.parent = parent;
+ this.listener = listener;
+
+ //Create a timer.
+ timer = new Timer(ONE_SECOND, new TimerListener());
+
+ progressMonitor = new ProgressMonitor(parent,
+ task.getDescription(),
+ "", 0, task.getLengthOfTask());
+ progressMonitor.setProgress(0);
+ progressMonitor.setMillisToPopup(ONE_SECOND);
+
+ task.go();
+ timer.start();
+ }
+
+ /**
+ * The actionPerformed method in this class
+ * is called each time the Timer "goes off".
+ */
+ class TimerListener implements ActionListener {
+ public void actionPerformed(ActionEvent evt) {
+ if (progressMonitor.isCanceled() || task.done()) {
+
+ boolean canceled = progressMonitor.isCanceled();
+ progressMonitor.close();
+ task.stop();
+ timer.stop();
+ if (canceled) {
+ listener.taskCanceled();
+ } else {
+ listener.taskFinished();
+ //Toolkit.getDefaultToolkit().beep();
+ }
+ } else {
+ progressMonitor.setNote(task.getMessage());
+ progressMonitor.setProgress(task.getCurrent());
+ }
+ }
+ }
+}
diff --git a/src/org/virion/jam/util/PrintUtilities.java b/src/org/virion/jam/util/PrintUtilities.java
new file mode 100644
index 0000000..41b18ad
--- /dev/null
+++ b/src/org/virion/jam/util/PrintUtilities.java
@@ -0,0 +1,137 @@
+package org.virion.jam.util;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+/**
+ * A simple utility class that lets you very simply print
+ * an arbitrary component. Just pass the component to the
+ * PrintUtilities.printComponent. The component you want to
+ * print doesn't need a print method and doesn't have to
+ * implement any interface or do anything special at all.
+ * <P>
+ * If you are going to be printing many times, it is marginally more
+ * efficient to first do the following:
+ * <PRE>
+ * PrintUtilities printHelper = new PrintUtilities(theComponent);
+ * </PRE>
+ * then later do printHelper.print(). But this is a very tiny
+ * difference, so in most cases just do the simpler
+ * PrintUtilities.printComponent(componentToBePrinted).
+ * <p/>
+ * 7/99 Marty Hall, http://www.apl.jhu.edu/~hall/java/
+ * May be freely used or adapted.
+ */
+
+public class PrintUtilities implements Printable {
+ private Component componentToBePrinted;
+ private boolean scaled;
+
+ public static void printComponent(Component c) {
+ new PrintUtilities(c).print();
+ }
+
+ public static void printComponentScaled(Component c) {
+ new PrintUtilities(c, true).print();
+ }
+
+ public PrintUtilities(Component componentToBePrinted) {
+ this.componentToBePrinted = componentToBePrinted;
+ }
+ public PrintUtilities(Component componentToBePrinted, boolean scaled) {
+ this(componentToBePrinted);
+ this.scaled = scaled;
+ }
+
+ public void print() {
+ PrinterJob printJob = PrinterJob.getPrinterJob();
+ printJob.setPrintable(this);
+ if (printJob.printDialog()) {
+// RepaintManager.currentManager(componentToBePrinted).paintDirtyRegions();
+
+ try {
+ printJob.print();
+ } catch (PrinterException pe) {
+ System.out.println("Error printing: " + pe);
+ }
+ }
+ }
+
+ public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
+ if(scaled) return printScaled(g, pageFormat, pageIndex);
+ if (pageIndex > 0) {
+ return (NO_SUCH_PAGE);
+ } else {
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+ disableDoubleBuffering(componentToBePrinted);
+ componentToBePrinted.paint(g2d);
+ enableDoubleBuffering(componentToBePrinted);
+ return (PAGE_EXISTS);
+ }
+ }
+
+ public int printScaled(Graphics g, PageFormat pageFormat, int pageIndex) {
+ return printScaled(componentToBePrinted,g, pageFormat, pageIndex);
+ }
+
+ public static int printScaled(Component componentToBePrinted, Graphics g, PageFormat pageFormat, int pageIndex) {
+ if (pageIndex > 0) {
+ return (NO_SUCH_PAGE);
+ } else {
+ //copy the graphics so we dont leave any residual scalings etc
+ //on the graphics.
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ double x0 = pageFormat.getImageableX();
+ double y0 = pageFormat.getImageableY();
+
+ double w0 = pageFormat.getImageableWidth();
+ double h0 = pageFormat.getImageableHeight();
+
+ double w1 = componentToBePrinted.getWidth();
+ double h1 = componentToBePrinted.getHeight();
+
+ double scale;
+
+ if (w0 / w1 < h0 / h1) {
+ scale = w0 / w1;
+ } else {
+ scale = h0 / h1;
+ }
+
+ g2d.translate(x0, y0);
+ g2d.scale(scale, scale);
+
+ disableDoubleBuffering(componentToBePrinted);
+ componentToBePrinted.paint(g2d);
+ enableDoubleBuffering(componentToBePrinted);
+
+ return (PAGE_EXISTS);
+ }
+ }
+
+
+ /**
+ * The speed and quality of printing suffers dramatically if
+ * any of the containers have double buffering turned on.
+ * So this turns if off globally.
+ */
+ public static void disableDoubleBuffering(Component c) {
+ RepaintManager currentManager = RepaintManager.currentManager(c);
+ currentManager.setDoubleBufferingEnabled(false);
+ }
+
+ /**
+ * Re-enables double buffering globally.
+ */
+
+ public static void enableDoubleBuffering(Component c) {
+ RepaintManager currentManager = RepaintManager.currentManager(c);
+ currentManager.setDoubleBufferingEnabled(true);
+ }
+}
diff --git a/src/org/virion/jam/util/SimpleListener.java b/src/org/virion/jam/util/SimpleListener.java
new file mode 100644
index 0000000..8735bbd
--- /dev/null
+++ b/src/org/virion/jam/util/SimpleListener.java
@@ -0,0 +1,10 @@
+package org.virion.jam.util;
+
+/**
+ * @author Richard Moir
+ * @version $Id: SimpleListener.java 482 2006-10-25 06:30:57Z twobeers $
+ */
+public interface SimpleListener {
+
+ void objectChanged();
+}
diff --git a/src/org/virion/jam/util/SimpleListenerManager.java b/src/org/virion/jam/util/SimpleListenerManager.java
new file mode 100644
index 0000000..d78e953
--- /dev/null
+++ b/src/org/virion/jam/util/SimpleListenerManager.java
@@ -0,0 +1,47 @@
+package org.virion.jam.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Richard Moir
+ * @version $Id: SimpleListenerManager.java 636 2007-01-31 03:15:55Z matt_kearse $
+ */
+public class SimpleListenerManager {
+
+ private List<SimpleListener> listeners = new ArrayList<SimpleListener>();
+
+ public SimpleListenerManager(SimpleListenerManager manager) {
+ this.listeners = new ArrayList<SimpleListener>(manager.listeners);
+ }
+
+ public SimpleListenerManager() {
+ }
+
+ public synchronized void add(SimpleListener listener) {
+ listeners.add(listener);
+ }
+
+ public synchronized void remove(SimpleListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ /**
+ * calls {@link org.virion.jam.util.SimpleListener#objectChanged()} on all listeners added using
+ * {@link #add(SimpleListener)} .
+ */
+ public synchronized void fire() {
+ for (SimpleListener simpleListener : listeners) {
+ simpleListener.objectChanged();
+ }
+ }
+
+ /**
+ * Get the number of listeners (those added, but not yet removed)
+ * @return
+ */
+ public synchronized int size () {
+ return listeners.size ();
+ }
+}
diff --git a/src/org/virion/jam/util/SimpleLongTask.java b/src/org/virion/jam/util/SimpleLongTask.java
new file mode 100644
index 0000000..c2fd188
--- /dev/null
+++ b/src/org/virion/jam/util/SimpleLongTask.java
@@ -0,0 +1,42 @@
+package org.virion.jam.util;
+
+public abstract class SimpleLongTask extends LongTask {
+
+ boolean background = false;
+ private SwingWorker worker = null;
+ public int current = 0;
+ public int length = 1;
+ public boolean pleaseStop = false;
+ public String message = "";
+ public String description = "";
+
+ /**
+ * Called to find out how much work needs
+ * to be done.
+ */
+ public int getLengthOfTask() {
+ return length;
+ }
+
+ /**
+ * Called to find out how much has been done.
+ */
+ public int getCurrent() {
+ return current;
+ }
+
+ /**
+ * Called to stop task.
+ */
+ public void stop() {
+ pleaseStop = true;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/src/org/virion/jam/util/SwingWorker.java b/src/org/virion/jam/util/SwingWorker.java
new file mode 100644
index 0000000..86ac902
--- /dev/null
+++ b/src/org/virion/jam/util/SwingWorker.java
@@ -0,0 +1,148 @@
+package org.virion.jam.util;
+
+import javax.swing.*;
+
+/**
+ * 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:
+ * <p/>
+ * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
+ * <p/>
+ * 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;
+ }
+
+ /**
+ * Allows the priority of the worker thread to be set.
+ */
+ public void setPriority(int priority) {
+ threadVar.get().setPriority(priority);
+ }
+
+ /**
+ * 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/src/org/virion/jam/util/TaskListener.java b/src/org/virion/jam/util/TaskListener.java
new file mode 100644
index 0000000..3463f6f
--- /dev/null
+++ b/src/org/virion/jam/util/TaskListener.java
@@ -0,0 +1,9 @@
+package org.virion.jam.util;
+
+
+public interface TaskListener {
+
+ void taskFinished();
+
+ void taskCanceled();
+}
diff --git a/src/org/virion/jam/util/Utils.java b/src/org/virion/jam/util/Utils.java
new file mode 100644
index 0000000..3a733ee
--- /dev/null
+++ b/src/org/virion/jam/util/Utils.java
@@ -0,0 +1,83 @@
+package org.virion.jam.util;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class Utils {
+
+ /**
+ * @return the value in a text field as a double.
+ * If the text field contents do not represent a valid double then the
+ * default value is inserted into the text field and returned.
+ */
+ public static double getDoubleFromTextField(JTextField textField, double defaultValue) {
+
+ double value = defaultValue;
+ try {
+ value = Double.parseDouble(textField.getText());
+ } catch (NumberFormatException nfe) {
+ textField.setText(defaultValue + "");
+ }
+ return value;
+ }
+
+ /**
+ * Center a component in reference to another,
+ * if the reference is null or not visible the screen is used as reference
+ */
+ public static void centerComponent(Component component, Component reference) {
+ Dimension componentSize = component.getSize();
+ Dimension referenceSize;
+ Point referencePosition;
+ if (reference != null && reference.isShowing()) {
+ referenceSize = reference.getSize();
+ referencePosition = reference.getLocationOnScreen();
+ } else {
+ referenceSize = Toolkit.getDefaultToolkit().getScreenSize();
+ referencePosition = new Point(0, 0);
+ }
+ component.setBounds(referencePosition.x
+ + Math.abs(referenceSize.width - componentSize.width) / 2,
+ referencePosition.y
+ + Math.abs(referenceSize.height - componentSize.height) / 2,
+ componentSize.width,
+ componentSize.height);
+ }
+
+ public static void showWaitCursor(Component component) {
+ showPredefinedCursor(component, Cursor.WAIT_CURSOR);
+ }
+
+ public static void showPredefinedCursor(Component component, int cursor) {
+ component.setCursor(Cursor.getPredefinedCursor(cursor));
+ }
+
+ public static void showDefaultCursor(Component component) {
+ showPredefinedCursor(component, Cursor.DEFAULT_CURSOR);
+ }
+
+ public static String getEnv(final String name) {
+ java.util.Properties jvmEnv = System.getProperties();
+ java.util.Properties envVars = new java.util.Properties();
+
+ try
+ {
+ if ( jvmEnv.getProperty( "os.name" ).toLowerCase().indexOf( "win" ) != -1 ) {
+ envVars.load( Runtime.getRuntime().exec( "set" ).getInputStream());
+ }
+ else {
+ try {
+ // ( jvmEnv.getProperty( "os.name" ).equalsIgnoreCase( "Linux" ) )
+ envVars.load( Runtime.getRuntime().exec( "/usr/bin/env").getInputStream() );
+ } catch( Throwable t ) {
+ envVars.load( Runtime.getRuntime().exec( "/bin/env").getInputStream() );
+ }
+ }
+ }
+ catch ( Throwable t )
+ {
+ t.printStackTrace();
+ }
+ return envVars.getProperty(name);
+ }
+}
\ No newline at end of file
diff --git a/src/org/xml/sax/AttributeList.java b/src/org/xml/sax/AttributeList.java
new file mode 100644
index 0000000..9285eac
--- /dev/null
+++ b/src/org/xml/sax/AttributeList.java
@@ -0,0 +1,193 @@
+// SAX Attribute List Interface.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: AttributeList.java,v 1.7 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Interface for an element's attribute specifications.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is the original SAX1 interface for reporting an element's
+ * attributes. Unlike the new {@link org.xml.sax.Attributes Attributes}
+ * interface, it does not support Namespace-related information.</p>
+ *
+ * <p>When an attribute list is supplied as part of a
+ * {@link org.xml.sax.DocumentHandler#startElement startElement}
+ * event, the list will return valid results only during the
+ * scope of the event; once the event handler returns control
+ * to the parser, the attribute list is invalid. To save a
+ * persistent copy of the attribute list, use the SAX1
+ * {@link org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
+ * helper class.</p>
+ *
+ * <p>An attribute list includes only attributes that have been
+ * specified or defaulted: #IMPLIED attributes will not be included.</p>
+ *
+ * <p>There are two ways for the SAX application to obtain information
+ * from the AttributeList. First, it can iterate through the entire
+ * list:</p>
+ *
+ * <pre>
+ * public void startElement (String name, AttributeList atts) {
+ * for (int i = 0; i < atts.getLength(); i++) {
+ * String name = atts.getName(i);
+ * String type = atts.getType(i);
+ * String value = atts.getValue(i);
+ * [...]
+ * }
+ * }
+ * </pre>
+ *
+ * <p>(Note that the result of getLength() will be zero if there
+ * are no attributes.)
+ *
+ * <p>As an alternative, the application can request the value or
+ * type of specific attributes:</p>
+ *
+ * <pre>
+ * public void startElement (String name, AttributeList atts) {
+ * String identifier = atts.getValue("id");
+ * String label = atts.getValue("label");
+ * [...]
+ * }
+ * </pre>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ * {@link org.xml.sax.Attributes Attributes}
+ * interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.DocumentHandler#startElement startElement
+ * @see org.xml.sax.helpers.AttributeListImpl AttributeListImpl
+ */
+public interface AttributeList {
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Iteration methods.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the number of attributes in this list.
+ *
+ * <p>The SAX parser may provide attributes in any
+ * arbitrary order, regardless of the order in which they were
+ * declared or specified. The number of attributes may be
+ * zero.</p>
+ *
+ * @return The number of attributes in the list.
+ */
+ public abstract int getLength ();
+
+
+ /**
+ * Return the name of an attribute in this list (by position).
+ *
+ * <p>The names must be unique: the SAX parser shall not include the
+ * same attribute twice. Attributes without values (those declared
+ * #IMPLIED without a value specified in the start tag) will be
+ * omitted from the list.</p>
+ *
+ * <p>If the attribute name has a namespace prefix, the prefix
+ * will still be attached.</p>
+ *
+ * @param i The index of the attribute in the list (starting at 0).
+ * @return The name of the indexed attribute, or null
+ * if the index is out of range.
+ * @see #getLength
+ */
+ public abstract String getName (int i);
+
+
+ /**
+ * Return the type of an attribute in the list (by position).
+ *
+ * <p>The attribute type is one of the strings "CDATA", "ID",
+ * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES",
+ * or "NOTATION" (always in upper case).</p>
+ *
+ * <p>If the parser has not read a declaration for the attribute,
+ * or if the parser does not report attribute types, then it must
+ * return the value "CDATA" as stated in the XML 1.0 Recommentation
+ * (clause 3.3.3, "Attribute-Value Normalization").</p>
+ *
+ * <p>For an enumerated attribute that is not a notation, the
+ * parser will report the type as "NMTOKEN".</p>
+ *
+ * @param i The index of the attribute in the list (starting at 0).
+ * @return The attribute type as a string, or
+ * null if the index is out of range.
+ * @see #getLength
+ * @see #getType(java.lang.String)
+ */
+ public abstract String getType (int i);
+
+
+ /**
+ * Return the value of an attribute in the list (by position).
+ *
+ * <p>If the attribute value is a list of tokens (IDREFS,
+ * ENTITIES, or NMTOKENS), the tokens will be concatenated
+ * into a single string separated by whitespace.</p>
+ *
+ * @param i The index of the attribute in the list (starting at 0).
+ * @return The attribute value as a string, or
+ * null if the index is out of range.
+ * @see #getLength
+ * @see #getValue(java.lang.String)
+ */
+ public abstract String getValue (int i);
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Lookup methods.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the type of an attribute in the list (by name).
+ *
+ * <p>The return value is the same as the return value for
+ * getType(int).</p>
+ *
+ * <p>If the attribute name has a namespace prefix in the document,
+ * the application must include the prefix here.</p>
+ *
+ * @param name The name of the attribute.
+ * @return The attribute type as a string, or null if no
+ * such attribute exists.
+ * @see #getType(int)
+ */
+ public abstract String getType (String name);
+
+
+ /**
+ * Return the value of an attribute in the list (by name).
+ *
+ * <p>The return value is the same as the return value for
+ * getValue(int).</p>
+ *
+ * <p>If the attribute name has a namespace prefix in the document,
+ * the application must include the prefix here.</p>
+ *
+ * @param name the name of the attribute to return
+ * @return The attribute value as a string, or null if
+ * no such attribute exists.
+ * @see #getValue(int)
+ */
+ public abstract String getValue (String name);
+
+}
+
+// end of AttributeList.java
diff --git a/src/org/xml/sax/Attributes.java b/src/org/xml/sax/Attributes.java
new file mode 100644
index 0000000..b25432d
--- /dev/null
+++ b/src/org/xml/sax/Attributes.java
@@ -0,0 +1,257 @@
+// Attributes.java - attribute list with Namespace support
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: Attributes.java,v 1.13 2004/03/18 12:28:05 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for a list of XML attributes.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This interface allows access to a list of attributes in
+ * three different ways:</p>
+ *
+ * <ol>
+ * <li>by attribute index;</li>
+ * <li>by Namespace-qualified name; or</li>
+ * <li>by qualified (prefixed) name.</li>
+ * </ol>
+ *
+ * <p>The list will not contain attributes that were declared
+ * #IMPLIED but not specified in the start tag. It will also not
+ * contain attributes used as Namespace declarations (xmlns*) unless
+ * the <code>http://xml.org/sax/features/namespace-prefixes</code>
+ * feature is set to <var>true</var> (it is <var>false</var> by
+ * default).
+ * Because SAX2 conforms to the original "Namespaces in XML"
+ * recommendation, it normally does not
+ * give namespace declaration attributes a namespace URI.
+ * </p>
+ *
+ * <p>Some SAX2 parsers may support using an optional feature flag
+ * (<code>http://xml.org/sax/features/xmlns-uris</code>) to request
+ * that those attributes be given URIs, conforming to a later
+ * backwards-incompatible revision of that recommendation. (The
+ * attribute's "local name" will be the prefix, or "xmlns" when
+ * defining a default element namespace.) For portability, handler
+ * code should always resolve that conflict, rather than requiring
+ * parsers that can change the setting of that feature flag. </p>
+ *
+ * <p>If the namespace-prefixes feature (see above) is
+ * <var>false</var>, access by qualified name may not be available; if
+ * the <code>http://xml.org/sax/features/namespaces</code> feature is
+ * <var>false</var>, access by Namespace-qualified names may not be
+ * available.</p>
+ *
+ * <p>This interface replaces the now-deprecated SAX1 {@link
+ * org.xml.sax.AttributeList AttributeList} interface, which does not
+ * contain Namespace support. In addition to Namespace support, it
+ * adds the <var>getIndex</var> methods (below).</p>
+ *
+ * <p>The order of attributes in the list is unspecified, and will
+ * vary from implementation to implementation.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.AttributesImpl
+ * @see org.xml.sax.ext.DeclHandler#attributeDecl
+ */
+public interface Attributes
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Indexed access.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the number of attributes in the list.
+ *
+ * <p>Once you know the number of attributes, you can iterate
+ * through the list.</p>
+ *
+ * @return The number of attributes in the list.
+ * @see #getURI(int)
+ * @see #getLocalName(int)
+ * @see #getQName(int)
+ * @see #getType(int)
+ * @see #getValue(int)
+ */
+ public abstract int getLength ();
+
+
+ /**
+ * Look up an attribute's Namespace URI by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The Namespace URI, or the empty string if none
+ * is available, or null if the index is out of
+ * range.
+ * @see #getLength
+ */
+ public abstract String getURI (int index);
+
+
+ /**
+ * Look up an attribute's local name by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The local name, or the empty string if Namespace
+ * processing is not being performed, or null
+ * if the index is out of range.
+ * @see #getLength
+ */
+ public abstract String getLocalName (int index);
+
+
+ /**
+ * Look up an attribute's XML qualified (prefixed) name by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The XML qualified name, or the empty string
+ * if none is available, or null if the index
+ * is out of range.
+ * @see #getLength
+ */
+ public abstract String getQName (int index);
+
+
+ /**
+ * Look up an attribute's type by index.
+ *
+ * <p>The attribute type is one of the strings "CDATA", "ID",
+ * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES",
+ * or "NOTATION" (always in upper case).</p>
+ *
+ * <p>If the parser has not read a declaration for the attribute,
+ * or if the parser does not report attribute types, then it must
+ * return the value "CDATA" as stated in the XML 1.0 Recommendation
+ * (clause 3.3.3, "Attribute-Value Normalization").</p>
+ *
+ * <p>For an enumerated attribute that is not a notation, the
+ * parser will report the type as "NMTOKEN".</p>
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's type as a string, or null if the
+ * index is out of range.
+ * @see #getLength
+ */
+ public abstract String getType (int index);
+
+
+ /**
+ * Look up an attribute's value by index.
+ *
+ * <p>If the attribute value is a list of tokens (IDREFS,
+ * ENTITIES, or NMTOKENS), the tokens will be concatenated
+ * into a single string with each token separated by a
+ * single space.</p>
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's value as a string, or null if the
+ * index is out of range.
+ * @see #getLength
+ */
+ public abstract String getValue (int index);
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Name-based query.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Look up the index of an attribute by Namespace name.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return The index of the attribute, or -1 if it does not
+ * appear in the list.
+ */
+ public int getIndex (String uri, String localName);
+
+
+ /**
+ * Look up the index of an attribute by XML qualified (prefixed) name.
+ *
+ * @param qName The qualified (prefixed) name.
+ * @return The index of the attribute, or -1 if it does not
+ * appear in the list.
+ */
+ public int getIndex (String qName);
+
+
+ /**
+ * Look up an attribute's type by Namespace name.
+ *
+ * <p>See {@link #getType(int) getType(int)} for a description
+ * of the possible types.</p>
+ *
+ * @param uri The Namespace URI, or the empty String if the
+ * name has no Namespace URI.
+ * @param localName The local name of the attribute.
+ * @return The attribute type as a string, or null if the
+ * attribute is not in the list or if Namespace
+ * processing is not being performed.
+ */
+ public abstract String getType (String uri, String localName);
+
+
+ /**
+ * Look up an attribute's type by XML qualified (prefixed) name.
+ *
+ * <p>See {@link #getType(int) getType(int)} for a description
+ * of the possible types.</p>
+ *
+ * @param qName The XML qualified name.
+ * @return The attribute type as a string, or null if the
+ * attribute is not in the list or if qualified names
+ * are not available.
+ */
+ public abstract String getType (String qName);
+
+
+ /**
+ * Look up an attribute's value by Namespace name.
+ *
+ * <p>See {@link #getValue(int) getValue(int)} for a description
+ * of the possible values.</p>
+ *
+ * @param uri The Namespace URI, or the empty String if the
+ * name has no Namespace URI.
+ * @param localName The local name of the attribute.
+ * @return The attribute value as a string, or null if the
+ * attribute is not in the list.
+ */
+ public abstract String getValue (String uri, String localName);
+
+
+ /**
+ * Look up an attribute's value by XML qualified (prefixed) name.
+ *
+ * <p>See {@link #getValue(int) getValue(int)} for a description
+ * of the possible values.</p>
+ *
+ * @param qName The XML qualified name.
+ * @return The attribute value as a string, or null if the
+ * attribute is not in the list or if qualified names
+ * are not available.
+ */
+ public abstract String getValue (String qName);
+
+}
+
+// end of Attributes.java
diff --git a/src/org/xml/sax/ContentHandler.java b/src/org/xml/sax/ContentHandler.java
new file mode 100644
index 0000000..3d95c5f
--- /dev/null
+++ b/src/org/xml/sax/ContentHandler.java
@@ -0,0 +1,419 @@
+// ContentHandler.java - handle main document content.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: ContentHandler.java,v 1.13 2004/04/26 17:50:49 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Receive notification of the logical content of a document.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is the main interface that most SAX applications
+ * implement: if the application needs to be informed of basic parsing
+ * events, it implements this interface and registers an instance with
+ * the SAX parser using the {@link org.xml.sax.XMLReader#setContentHandler
+ * setContentHandler} method. The parser uses the instance to report
+ * basic document-related events like the start and end of elements
+ * and character data.</p>
+ *
+ * <p>The order of events in this interface is very important, and
+ * mirrors the order of information in the document itself. For
+ * example, all of an element's content (character data, processing
+ * instructions, and/or subelements) will appear, in order, between
+ * the startElement event and the corresponding endElement event.</p>
+ *
+ * <p>This interface is similar to the now-deprecated SAX 1.0
+ * DocumentHandler interface, but it adds support for Namespaces
+ * and for reporting skipped entities (in non-validating XML
+ * processors).</p>
+ *
+ * <p>Implementors should note that there is also a
+ * <code>ContentHandler</code> class in the <code>java.net</code>
+ * package; that means that it's probably a bad idea to do</p>
+ *
+ * <pre>import java.net.*;
+ * import org.xml.sax.*;
+ * </pre>
+ *
+ * <p>In fact, "import ...*" is usually a sign of sloppy programming
+ * anyway, so the user should consider this a feature rather than a
+ * bug.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public interface ContentHandler
+{
+
+ /**
+ * Receive an object for locating the origin of SAX document events.
+ *
+ * <p>SAX parsers are strongly encouraged (though not absolutely
+ * required) to supply a locator: if it does so, it must supply
+ * the locator to the application by invoking this method before
+ * invoking any of the other methods in the ContentHandler
+ * interface.</p>
+ *
+ * <p>The locator allows the application to determine the end
+ * position of any document-related event, even if the parser is
+ * not reporting an error. Typically, the application will
+ * use this information for reporting its own errors (such as
+ * character content that does not match an application's
+ * business rules). The information returned by the locator
+ * is probably not sufficient for use with a search engine.</p>
+ *
+ * <p>Note that the locator will return correct information only
+ * during the invocation SAX event callbacks after
+ * {@link #startDocument startDocument} returns and before
+ * {@link #endDocument endDocument} is called. The
+ * application should not attempt to use it at any other time.</p>
+ *
+ * @param locator an object that can return the location of
+ * any SAX document event
+ * @see org.xml.sax.Locator
+ */
+ public void setDocumentLocator (Locator locator);
+
+
+ /**
+ * Receive notification of the beginning of a document.
+ *
+ * <p>The SAX parser will invoke this method only once, before any
+ * other event callbacks (except for {@link #setDocumentLocator
+ * setDocumentLocator}).</p>
+ *
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ * @see #endDocument
+ */
+ public void startDocument ()
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the end of a document.
+ *
+ * <p><strong>There is an apparent contradiction between the
+ * documentation for this method and the documentation for {@link
+ * org.xml.sax.ErrorHandler#fatalError}. Until this ambiguity is
+ * resolved in a future major release, clients should make no
+ * assumptions about whether endDocument() will or will not be
+ * invoked when the parser has reported a fatalError() or thrown
+ * an exception.</strong></p>
+ *
+ * <p>The SAX parser will invoke this method only once, and it will
+ * be the last method invoked during the parse. The parser shall
+ * not invoke this method until it has either abandoned parsing
+ * (because of an unrecoverable error) or reached the end of
+ * input.</p>
+ *
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ * @see #startDocument
+ */
+ public void endDocument()
+ throws SAXException;
+
+
+ /**
+ * Begin the scope of a prefix-URI Namespace mapping.
+ *
+ * <p>The information from this event is not necessary for
+ * normal Namespace processing: the SAX XML reader will
+ * automatically replace prefixes for element and attribute
+ * names when the <code>http://xml.org/sax/features/namespaces</code>
+ * feature is <var>true</var> (the default).</p>
+ *
+ * <p>There are cases, however, when applications need to
+ * use prefixes in character data or in attribute values,
+ * where they cannot safely be expanded automatically; the
+ * start/endPrefixMapping event supplies the information
+ * to the application to expand prefixes in those contexts
+ * itself, if necessary.</p>
+ *
+ * <p>Note that start/endPrefixMapping events are not
+ * guaranteed to be properly nested relative to each other:
+ * all startPrefixMapping events will occur immediately before the
+ * corresponding {@link #startElement startElement} event,
+ * and all {@link #endPrefixMapping endPrefixMapping}
+ * events will occur immediately after the corresponding
+ * {@link #endElement endElement} event,
+ * but their order is not otherwise
+ * guaranteed.</p>
+ *
+ * <p>There should never be start/endPrefixMapping events for the
+ * "xml" prefix, since it is predeclared and immutable.</p>
+ *
+ * @param prefix the Namespace prefix being declared.
+ * An empty string is used for the default element namespace,
+ * which has no prefix.
+ * @param uri the Namespace URI the prefix is mapped to
+ * @throws org.xml.sax.SAXException the client may throw
+ * an exception during processing
+ * @see #endPrefixMapping
+ * @see #startElement
+ */
+ public void startPrefixMapping (String prefix, String uri)
+ throws SAXException;
+
+
+ /**
+ * End the scope of a prefix-URI mapping.
+ *
+ * <p>See {@link #startPrefixMapping startPrefixMapping} for
+ * details. These events will always occur immediately after the
+ * corresponding {@link #endElement endElement} event, but the order of
+ * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
+ * guaranteed.</p>
+ *
+ * @param prefix the prefix that was being mapped.
+ * This is the empty string when a default mapping scope ends.
+ * @throws org.xml.sax.SAXException the client may throw
+ * an exception during processing
+ * @see #startPrefixMapping
+ * @see #endElement
+ */
+ public void endPrefixMapping (String prefix)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the beginning of an element.
+ *
+ * <p>The Parser will invoke this method at the beginning of every
+ * element in the XML document; there will be a corresponding
+ * {@link #endElement endElement} event for every startElement event
+ * (even when the element is empty). All of the element's content will be
+ * reported, in order, before the corresponding endElement
+ * event.</p>
+ *
+ * <p>This event allows up to three name components for each
+ * element:</p>
+ *
+ * <ol>
+ * <li>the Namespace URI;</li>
+ * <li>the local name; and</li>
+ * <li>the qualified (prefixed) name.</li>
+ * </ol>
+ *
+ * <p>Any or all of these may be provided, depending on the
+ * values of the <var>http://xml.org/sax/features/namespaces</var>
+ * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
+ * properties:</p>
+ *
+ * <ul>
+ * <li>the Namespace URI and local name are required when
+ * the namespaces property is <var>true</var> (the default), and are
+ * optional when the namespaces property is <var>false</var> (if one is
+ * specified, both must be);</li>
+ * <li>the qualified name is required when the namespace-prefixes property
+ * is <var>true</var>, and is optional when the namespace-prefixes property
+ * is <var>false</var> (the default).</li>
+ * </ul>
+ *
+ * <p>Note that the attribute list provided will contain only
+ * attributes with explicit values (specified or defaulted):
+ * #IMPLIED attributes will be omitted. The attribute list
+ * will contain attributes used for Namespace declarations
+ * (xmlns* attributes) only if the
+ * <code>http://xml.org/sax/features/namespace-prefixes</code>
+ * property is true (it is false by default, and support for a
+ * true value is optional).</p>
+ *
+ * <p>Like {@link #characters characters()}, attribute values may have
+ * characters that need more than one <code>char</code> value. </p>
+ *
+ * @param uri the Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed
+ * @param localName the local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed
+ * @param qName the qualified name (with prefix), or the
+ * empty string if qualified names are not available
+ * @param atts the attributes attached to the element. If
+ * there are no attributes, it shall be an empty
+ * Attributes object. The value of this object after
+ * startElement returns is undefined
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ * @see #endElement
+ * @see org.xml.sax.Attributes
+ * @see org.xml.sax.helpers.AttributesImpl
+ */
+ public void startElement (String uri, String localName,
+ String qName, Attributes atts)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the end of an element.
+ *
+ * <p>The SAX parser will invoke this method at the end of every
+ * element in the XML document; there will be a corresponding
+ * {@link #startElement startElement} event for every endElement
+ * event (even when the element is empty).</p>
+ *
+ * <p>For information on the names, see startElement.</p>
+ *
+ * @param uri the Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed
+ * @param localName the local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed
+ * @param qName the qualified XML name (with prefix), or the
+ * empty string if qualified names are not available
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ */
+ public void endElement (String uri, String localName,
+ String qName)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of character data.
+ *
+ * <p>The Parser will call this method to report each chunk of
+ * character data. SAX parsers may return all contiguous character
+ * data in a single chunk, or they may split it into several
+ * chunks; however, all of the characters in any single event
+ * must come from the same external entity so that the Locator
+ * provides useful information.</p>
+ *
+ * <p>The application must not attempt to read from the array
+ * outside of the specified range.</p>
+ *
+ * <p>Individual characters may consist of more than one Java
+ * <code>char</code> value. There are two important cases where this
+ * happens, because characters can't be represented in just sixteen bits.
+ * In one case, characters are represented in a <em>Surrogate Pair</em>,
+ * using two special Unicode values. Such characters are in the so-called
+ * "Astral Planes", with a code point above U+FFFF. A second case involves
+ * composite characters, such as a base character combining with one or
+ * more accent characters. </p>
+ *
+ * <p> Your code should not assume that algorithms using
+ * <code>char</code>-at-a-time idioms will be working in character
+ * units; in some cases they will split characters. This is relevant
+ * wherever XML permits arbitrary characters, such as attribute values,
+ * processing instruction data, and comments as well as in data reported
+ * from this method. It's also generally relevant whenever Java code
+ * manipulates internationalized text; the issue isn't unique to XML.</p>
+ *
+ * <p>Note that some parsers will report whitespace in element
+ * content using the {@link #ignorableWhitespace ignorableWhitespace}
+ * method rather than this one (validating parsers <em>must</em>
+ * do so).</p>
+ *
+ * @param ch the characters from the XML document
+ * @param start the start position in the array
+ * @param length the number of characters to read from the array
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ * @see #ignorableWhitespace
+ * @see org.xml.sax.Locator
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of ignorable whitespace in element content.
+ *
+ * <p>Validating Parsers must use this method to report each chunk
+ * of whitespace in element content (see the W3C XML 1.0
+ * recommendation, section 2.10): non-validating parsers may also
+ * use this method if they are capable of parsing and using
+ * content models.</p>
+ *
+ * <p>SAX parsers may return all contiguous whitespace in a single
+ * chunk, or they may split it into several chunks; however, all of
+ * the characters in any single event must come from the same
+ * external entity, so that the Locator provides useful
+ * information.</p>
+ *
+ * <p>The application must not attempt to read from the array
+ * outside of the specified range.</p>
+ *
+ * @param ch the characters from the XML document
+ * @param start the start position in the array
+ * @param length the number of characters to read from the array
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ * @see #characters
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a processing instruction.
+ *
+ * <p>The Parser will invoke this method once for each processing
+ * instruction found: note that processing instructions may occur
+ * before or after the main document element.</p>
+ *
+ * <p>A SAX parser must never report an XML declaration (XML 1.0,
+ * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
+ * using this method.</p>
+ *
+ * <p>Like {@link #characters characters()}, processing instruction
+ * data may have characters that need more than one <code>char</code>
+ * value. </p>
+ *
+ * @param target the processing instruction target
+ * @param data the processing instruction data, or null if
+ * none was supplied. The data does not include any
+ * whitespace separating it from the target
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a skipped entity.
+ * This is not called for entity references within markup constructs
+ * such as element start tags or markup declarations. (The XML
+ * recommendation requires reporting skipped external entities.
+ * SAX also reports internal entity expansion/non-expansion, except
+ * within markup constructs.)
+ *
+ * <p>The Parser will invoke this method each time the entity is
+ * skipped. Non-validating processors may skip entities if they
+ * have not seen the declarations (because, for example, the
+ * entity was declared in an external DTD subset). All processors
+ * may skip external entities, depending on the values of the
+ * <code>http://xml.org/sax/features/external-general-entities</code>
+ * and the
+ * <code>http://xml.org/sax/features/external-parameter-entities</code>
+ * properties.</p>
+ *
+ * @param name the name of the skipped entity. If it is a
+ * parameter entity, the name will begin with '%', and if
+ * it is the external DTD subset, it will be the string
+ * "[dtd]"
+ * @throws org.xml.sax.SAXException any SAX exception, possibly
+ * wrapping another exception
+ */
+ public void skippedEntity (String name)
+ throws SAXException;
+}
+
+// end of ContentHandler.java
diff --git a/src/org/xml/sax/DTDHandler.java b/src/org/xml/sax/DTDHandler.java
new file mode 100644
index 0000000..e62222a
--- /dev/null
+++ b/src/org/xml/sax/DTDHandler.java
@@ -0,0 +1,117 @@
+// SAX DTD handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: DTDHandler.java,v 1.8 2002/01/30 21:13:43 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Receive notification of basic DTD-related events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs information about notations and
+ * unparsed entities, then the application implements this
+ * interface and registers an instance with the SAX parser using
+ * the parser's setDTDHandler method. The parser uses the
+ * instance to report notation and unparsed entity declarations to
+ * the application.</p>
+ *
+ * <p>Note that this interface includes only those DTD events that
+ * the XML recommendation <em>requires</em> processors to report:
+ * notation and unparsed entity declarations.</p>
+ *
+ * <p>The SAX parser may report these events in any order, regardless
+ * of the order in which the notations and unparsed entities were
+ * declared; however, all DTD events must be reported after the
+ * document handler's startDocument event, and before the first
+ * startElement event.
+ * (If the {@link org.xml.sax.ext.LexicalHandler LexicalHandler} is
+ * used, these events must also be reported before the endDTD event.)
+ * </p>
+ *
+ * <p>It is up to the application to store the information for
+ * future use (perhaps in a hash table or object tree).
+ * If the application encounters attributes of type "NOTATION",
+ * "ENTITY", or "ENTITIES", it can use the information that it
+ * obtained through this interface to find the entity and/or
+ * notation corresponding with the attribute value.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#setDTDHandler
+ */
+public interface DTDHandler {
+
+
+ /**
+ * Receive notification of a notation declaration event.
+ *
+ * <p>It is up to the application to record the notation for later
+ * reference, if necessary;
+ * notations may appear as attribute values and in unparsed entity
+ * declarations, and are sometime used with processing instruction
+ * target names.</p>
+ *
+ * <p>At least one of publicId and systemId must be non-null.
+ * If a system identifier is present, and it is a URL, the SAX
+ * parser must resolve it fully before passing it to the
+ * application through this event.</p>
+ *
+ * <p>There is no guarantee that the notation declaration will be
+ * reported before any unparsed entities that use it.</p>
+ *
+ * @param name The notation name.
+ * @param publicId The notation's public identifier, or null if
+ * none was given.
+ * @param systemId The notation's system identifier, or null if
+ * none was given.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see #unparsedEntityDecl
+ * @see org.xml.sax.Attributes
+ */
+ public abstract void notationDecl (String name,
+ String publicId,
+ String systemId)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of an unparsed entity declaration event.
+ *
+ * <p>Note that the notation name corresponds to a notation
+ * reported by the {@link #notationDecl notationDecl} event.
+ * It is up to the application to record the entity for later
+ * reference, if necessary;
+ * unparsed entities may appear as attribute values.
+ * </p>
+ *
+ * <p>If the system identifier is a URL, the parser must resolve it
+ * fully before passing it to the application.</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @param name The unparsed entity's name.
+ * @param publicId The entity's public identifier, or null if none
+ * was given.
+ * @param systemId The entity's system identifier.
+ * @param notationName The name of the associated notation.
+ * @see #notationDecl
+ * @see org.xml.sax.Attributes
+ */
+ public abstract void unparsedEntityDecl (String name,
+ String publicId,
+ String systemId,
+ String notationName)
+ throws SAXException;
+
+}
+
+// end of DTDHandler.java
diff --git a/src/org/xml/sax/DocumentHandler.java b/src/org/xml/sax/DocumentHandler.java
new file mode 100644
index 0000000..d70ae74
--- /dev/null
+++ b/src/org/xml/sax/DocumentHandler.java
@@ -0,0 +1,232 @@
+// SAX document handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: DocumentHandler.java,v 1.6 2002/01/30 21:13:43 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Receive notification of general document events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This was the main event-handling interface for SAX1; in
+ * SAX2, it has been replaced by {@link org.xml.sax.ContentHandler
+ * ContentHandler}, which provides Namespace support and reporting
+ * of skipped entities. This interface is included in SAX2 only
+ * to support legacy SAX1 applications.</p>
+ *
+ * <p>The order of events in this interface is very important, and
+ * mirrors the order of information in the document itself. For
+ * example, all of an element's content (character data, processing
+ * instructions, and/or subelements) will appear, in order, between
+ * the startElement event and the corresponding endElement event.</p>
+ *
+ * <p>Application writers who do not want to implement the entire
+ * interface can derive a class from HandlerBase, which implements
+ * the default functionality; parser writers can instantiate
+ * HandlerBase to obtain a default handler. The application can find
+ * the location of any document event using the Locator interface
+ * supplied by the Parser through the setDocumentLocator method.</p>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ * {@link org.xml.sax.ContentHandler ContentHandler}
+ * interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Parser#setDocumentHandler
+ * @see org.xml.sax.Locator
+ * @see org.xml.sax.HandlerBase
+ */
+public interface DocumentHandler {
+
+
+ /**
+ * Receive an object for locating the origin of SAX document events.
+ *
+ * <p>SAX parsers are strongly encouraged (though not absolutely
+ * required) to supply a locator: if it does so, it must supply
+ * the locator to the application by invoking this method before
+ * invoking any of the other methods in the DocumentHandler
+ * interface.</p>
+ *
+ * <p>The locator allows the application to determine the end
+ * position of any document-related event, even if the parser is
+ * not reporting an error. Typically, the application will
+ * use this information for reporting its own errors (such as
+ * character content that does not match an application's
+ * business rules). The information returned by the locator
+ * is probably not sufficient for use with a search engine.</p>
+ *
+ * <p>Note that the locator will return correct information only
+ * during the invocation of the events in this interface. The
+ * application should not attempt to use it at any other time.</p>
+ *
+ * @param locator An object that can return the location of
+ * any SAX document event.
+ * @see org.xml.sax.Locator
+ */
+ public abstract void setDocumentLocator (Locator locator);
+
+
+ /**
+ * Receive notification of the beginning of a document.
+ *
+ * <p>The SAX parser will invoke this method only once, before any
+ * other methods in this interface or in DTDHandler (except for
+ * setDocumentLocator).</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ */
+ public abstract void startDocument ()
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the end of a document.
+ *
+ * <p>The SAX parser will invoke this method only once, and it will
+ * be the last method invoked during the parse. The parser shall
+ * not invoke this method until it has either abandoned parsing
+ * (because of an unrecoverable error) or reached the end of
+ * input.</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ */
+ public abstract void endDocument ()
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the beginning of an element.
+ *
+ * <p>The Parser will invoke this method at the beginning of every
+ * element in the XML document; there will be a corresponding
+ * endElement() event for every startElement() event (even when the
+ * element is empty). All of the element's content will be
+ * reported, in order, before the corresponding endElement()
+ * event.</p>
+ *
+ * <p>If the element name has a namespace prefix, the prefix will
+ * still be attached. Note that the attribute list provided will
+ * contain only attributes with explicit values (specified or
+ * defaulted): #IMPLIED attributes will be omitted.</p>
+ *
+ * @param name The element type name.
+ * @param atts The attributes attached to the element, if any.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see #endElement
+ * @see org.xml.sax.AttributeList
+ */
+ public abstract void startElement (String name, AttributeList atts)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of the end of an element.
+ *
+ * <p>The SAX parser will invoke this method at the end of every
+ * element in the XML document; there will be a corresponding
+ * startElement() event for every endElement() event (even when the
+ * element is empty).</p>
+ *
+ * <p>If the element name has a namespace prefix, the prefix will
+ * still be attached to the name.</p>
+ *
+ * @param name The element type name
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ */
+ public abstract void endElement (String name)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of character data.
+ *
+ * <p>The Parser will call this method to report each chunk of
+ * character data. SAX parsers may return all contiguous character
+ * data in a single chunk, or they may split it into several
+ * chunks; however, all of the characters in any single event
+ * must come from the same external entity, so that the Locator
+ * provides useful information.</p>
+ *
+ * <p>The application must not attempt to read from the array
+ * outside of the specified range.</p>
+ *
+ * <p>Note that some parsers will report whitespace using the
+ * ignorableWhitespace() method rather than this one (validating
+ * parsers must do so).</p>
+ *
+ * @param ch The characters from the XML document.
+ * @param start The start position in the array.
+ * @param length The number of characters to read from the array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see #ignorableWhitespace
+ * @see org.xml.sax.Locator
+ */
+ public abstract void characters (char ch[], int start, int length)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of ignorable whitespace in element content.
+ *
+ * <p>Validating Parsers must use this method to report each chunk
+ * of ignorable whitespace (see the W3C XML 1.0 recommendation,
+ * section 2.10): non-validating parsers may also use this method
+ * if they are capable of parsing and using content models.</p>
+ *
+ * <p>SAX parsers may return all contiguous whitespace in a single
+ * chunk, or they may split it into several chunks; however, all of
+ * the characters in any single event must come from the same
+ * external entity, so that the Locator provides useful
+ * information.</p>
+ *
+ * <p>The application must not attempt to read from the array
+ * outside of the specified range.</p>
+ *
+ * @param ch The characters from the XML document.
+ * @param start The start position in the array.
+ * @param length The number of characters to read from the array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see #characters
+ */
+ public abstract void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a processing instruction.
+ *
+ * <p>The Parser will invoke this method once for each processing
+ * instruction found: note that processing instructions may occur
+ * before or after the main document element.</p>
+ *
+ * <p>A SAX parser should never report an XML declaration (XML 1.0,
+ * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
+ * using this method.</p>
+ *
+ * @param target The processing instruction target.
+ * @param data The processing instruction data, or null if
+ * none was supplied.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ */
+ public abstract void processingInstruction (String target, String data)
+ throws SAXException;
+
+}
+
+// end of DocumentHandler.java
diff --git a/src/org/xml/sax/EntityResolver.java b/src/org/xml/sax/EntityResolver.java
new file mode 100644
index 0000000..65b04eb
--- /dev/null
+++ b/src/org/xml/sax/EntityResolver.java
@@ -0,0 +1,119 @@
+// SAX entity resolver.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: EntityResolver.java,v 1.10 2002/01/30 21:13:44 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+
+
+/**
+ * Basic interface for resolving entities.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs to implement customized handling
+ * for external entities, it must implement this interface and
+ * register an instance with the SAX driver using the
+ * {@link org.xml.sax.XMLReader#setEntityResolver setEntityResolver}
+ * method.</p>
+ *
+ * <p>The XML reader will then allow the application to intercept any
+ * external entities (including the external DTD subset and external
+ * parameter entities, if any) before including them.</p>
+ *
+ * <p>Many SAX applications will not need to implement this interface,
+ * but it will be especially useful for applications that build
+ * XML documents from databases or other specialised input sources,
+ * or for applications that use URI types other than URLs.</p>
+ *
+ * <p>The following resolver would provide the application
+ * with a special character stream for the entity with the system
+ * identifier "http://www.myhost.com/today":</p>
+ *
+ * <pre>
+ * import org.xml.sax.EntityResolver;
+ * import org.xml.sax.InputSource;
+ *
+ * public class MyResolver implements EntityResolver {
+ * public InputSource resolveEntity (String publicId, String systemId)
+ * {
+ * if (systemId.equals("http://www.myhost.com/today")) {
+ * // return a special input source
+ * MyReader reader = new MyReader();
+ * return new InputSource(reader);
+ * } else {
+ * // use the default behaviour
+ * return null;
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <p>The application can also use this interface to redirect system
+ * identifiers to local URIs or to look up replacements in a catalog
+ * (possibly by using the public identifier).</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ * @see org.xml.sax.InputSource
+ */
+public interface EntityResolver {
+
+
+ /**
+ * Allow the application to resolve external entities.
+ *
+ * <p>The parser will call this method before opening any external
+ * entity except the top-level document entity. Such entities include
+ * the external DTD subset and external parameter entities referenced
+ * within the DTD (in either case, only if the parser reads external
+ * parameter entities), and external general entities referenced
+ * within the document element (if the parser reads external general
+ * entities). The application may request that the parser locate
+ * the entity itself, that it use an alternative URI, or that it
+ * use data provided by the application (as a character or byte
+ * input stream).</p>
+ *
+ * <p>Application writers can use this method to redirect external
+ * system identifiers to secure and/or local URIs, to look up
+ * public identifiers in a catalogue, or to read an entity from a
+ * database or other input source (including, for example, a dialog
+ * box). Neither XML nor SAX specifies a preferred policy for using
+ * public or system IDs to resolve resources. However, SAX specifies
+ * how to interpret any InputSource returned by this method, and that
+ * if none is returned, then the system ID will be dereferenced as
+ * a URL. </p>
+ *
+ * <p>If the system identifier is a URL, the SAX parser must
+ * resolve it fully before reporting it to the application.</p>
+ *
+ * @param publicId The public identifier of the external entity
+ * being referenced, or null if none was supplied.
+ * @param systemId The system identifier of the external entity
+ * being referenced.
+ * @return An InputSource object describing the new input source,
+ * or null to request that the parser open a regular
+ * URI connection to the system identifier.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException A Java-specific IO exception,
+ * possibly the result of creating a new InputStream
+ * or Reader for the InputSource.
+ * @see org.xml.sax.InputSource
+ */
+ public abstract InputSource resolveEntity (String publicId,
+ String systemId)
+ throws SAXException, IOException;
+
+}
+
+// end of EntityResolver.java
diff --git a/src/org/xml/sax/ErrorHandler.java b/src/org/xml/sax/ErrorHandler.java
new file mode 100644
index 0000000..37d2501
--- /dev/null
+++ b/src/org/xml/sax/ErrorHandler.java
@@ -0,0 +1,139 @@
+// SAX error handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: ErrorHandler.java,v 1.10 2004/03/08 13:01:00 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Basic interface for SAX error handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs to implement customized error
+ * handling, it must implement this interface and then register an
+ * instance with the XML reader using the
+ * {@link org.xml.sax.XMLReader#setErrorHandler setErrorHandler}
+ * method. The parser will then report all errors and warnings
+ * through this interface.</p>
+ *
+ * <p><strong>WARNING:</strong> If an application does <em>not</em>
+ * register an ErrorHandler, XML parsing errors will go unreported,
+ * except that <em>SAXParseException</em>s will be thrown for fatal errors.
+ * In order to detect validity errors, an ErrorHandler that does something
+ * with {@link #error error()} calls must be registered.</p>
+ *
+ * <p>For XML processing errors, a SAX driver must use this interface
+ * in preference to throwing an exception: it is up to the application
+ * to decide whether to throw an exception for different types of
+ * errors and warnings. Note, however, that there is no requirement that
+ * the parser continue to report additional errors after a call to
+ * {@link #fatalError fatalError}. In other words, a SAX driver class
+ * may throw an exception after reporting any fatalError.
+ * Also parsers may throw appropriate exceptions for non-XML errors.
+ * For example, {@link XMLReader#parse XMLReader.parse()} would throw
+ * an IOException for errors accessing entities or the document.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLReader#setErrorHandler
+ * @see org.xml.sax.SAXParseException
+ */
+public interface ErrorHandler {
+
+
+ /**
+ * Receive notification of a warning.
+ *
+ * <p>SAX parsers will use this method to report conditions that
+ * are not errors or fatal errors as defined by the XML
+ * recommendation. The default behaviour is to take no
+ * action.</p>
+ *
+ * <p>The SAX parser must continue to provide normal parsing events
+ * after invoking this method: it should still be possible for the
+ * application to process the document through to the end.</p>
+ *
+ * <p>Filters may use this method to report other, non-XML warnings
+ * as well.</p>
+ *
+ * @param exception The warning information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void warning (SAXParseException exception)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a recoverable error.
+ *
+ * <p>This corresponds to the definition of "error" in section 1.2
+ * of the W3C XML 1.0 Recommendation. For example, a validating
+ * parser would use this callback to report the violation of a
+ * validity constraint. The default behaviour is to take no
+ * action.</p>
+ *
+ * <p>The SAX parser must continue to provide normal parsing
+ * events after invoking this method: it should still be possible
+ * for the application to process the document through to the end.
+ * If the application cannot do so, then the parser should report
+ * a fatal error even if the XML recommendation does not require
+ * it to do so.</p>
+ *
+ * <p>Filters may use this method to report other, non-XML errors
+ * as well.</p>
+ *
+ * @param exception The error information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void error (SAXParseException exception)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a non-recoverable error.
+ *
+ * <p><strong>There is an apparent contradiction between the
+ * documentation for this method and the documentation for {@link
+ * org.xml.sax.ContentHandler#endDocument}. Until this ambiguity
+ * is resolved in a future major release, clients should make no
+ * assumptions about whether endDocument() will or will not be
+ * invoked when the parser has reported a fatalError() or thrown
+ * an exception.</strong></p>
+ *
+ * <p>This corresponds to the definition of "fatal error" in
+ * section 1.2 of the W3C XML 1.0 Recommendation. For example, a
+ * parser would use this callback to report the violation of a
+ * well-formedness constraint.</p>
+ *
+ * <p>The application must assume that the document is unusable
+ * after the parser has invoked this method, and should continue
+ * (if at all) only for the sake of collecting additional error
+ * messages: in fact, SAX parsers are free to stop reporting any
+ * other events once this method has been invoked.</p>
+ *
+ * @param exception The error information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void fatalError (SAXParseException exception)
+ throws SAXException;
+
+}
+
+// end of ErrorHandler.java
diff --git a/src/org/xml/sax/HandlerBase.java b/src/org/xml/sax/HandlerBase.java
new file mode 100644
index 0000000..186cf0c
--- /dev/null
+++ b/src/org/xml/sax/HandlerBase.java
@@ -0,0 +1,369 @@
+// SAX default handler base class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: HandlerBase.java,v 1.7 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Default base class for handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class implements the default behaviour for four SAX1
+ * interfaces: EntityResolver, DTDHandler, DocumentHandler,
+ * and ErrorHandler. It is now obsolete, but is included in SAX2 to
+ * support legacy SAX1 applications. SAX2 applications should use
+ * the {@link org.xml.sax.helpers.DefaultHandler DefaultHandler}
+ * class instead.</p>
+ *
+ * <p>Application writers can extend this class when they need to
+ * implement only part of an interface; parser writers can
+ * instantiate this class to provide default handlers when the
+ * application has not supplied its own.</p>
+ *
+ * <p>Note that the use of this class is optional.</p>
+ *
+ * @deprecated This class works with the deprecated
+ * {@link org.xml.sax.DocumentHandler DocumentHandler}
+ * interface. It has been replaced by the SAX2
+ * {@link org.xml.sax.helpers.DefaultHandler DefaultHandler}
+ * class.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.DocumentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class HandlerBase
+ implements EntityResolver, DTDHandler, DocumentHandler, ErrorHandler
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of the EntityResolver interface.
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Resolve an external entity.
+ *
+ * <p>Always return null, so that the parser will use the system
+ * identifier provided in the XML document. This method implements
+ * the SAX default behaviour: application writers can override it
+ * in a subclass to do special translations such as catalog lookups
+ * or URI redirection.</p>
+ *
+ * @param publicId The public identifer, or null if none is
+ * available.
+ * @param systemId The system identifier provided in the XML
+ * document.
+ * @return The new input source, or null to require the
+ * default behaviour.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.EntityResolver#resolveEntity
+ */
+ public InputSource resolveEntity (String publicId, String systemId)
+ throws SAXException
+ {
+ return null;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of DTDHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive notification of a notation declaration.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass if they wish to keep track of the notations
+ * declared in a document.</p>
+ *
+ * @param name The notation name.
+ * @param publicId The notation public identifier, or null if not
+ * available.
+ * @param systemId The notation system identifier.
+ * @see org.xml.sax.DTDHandler#notationDecl
+ */
+ public void notationDecl (String name, String publicId, String systemId)
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of an unparsed entity declaration.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to keep track of the unparsed entities
+ * declared in a document.</p>
+ *
+ * @param name The entity name.
+ * @param publicId The entity public identifier, or null if not
+ * available.
+ * @param systemId The entity system identifier.
+ * @param notationName The name of the associated notation.
+ * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+ */
+ public void unparsedEntityDecl (String name, String publicId,
+ String systemId, String notationName)
+ {
+ // no op
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of DocumentHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive a Locator object for document events.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass if they wish to store the locator for use
+ * with other document events.</p>
+ *
+ * @param locator A locator for all SAX document events.
+ * @see org.xml.sax.DocumentHandler#setDocumentLocator
+ * @see org.xml.sax.Locator
+ */
+ public void setDocumentLocator (Locator locator)
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the beginning of the document.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the beginning
+ * of a document (such as allocating the root node of a tree or
+ * creating an output file).</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#startDocument
+ */
+ public void startDocument ()
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the end of the document.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the beginning
+ * of a document (such as finalising a tree or closing an output
+ * file).</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#endDocument
+ */
+ public void endDocument ()
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the start of an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the start of
+ * each element (such as allocating a new tree node or writing
+ * output to a file).</p>
+ *
+ * @param name The element type name.
+ * @param attributes The specified or defaulted attributes.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#startElement
+ */
+ public void startElement (String name, AttributeList attributes)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the end of an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the end of
+ * each element (such as finalising a tree node or writing
+ * output to a file).</p>
+ *
+ * @param name the element name
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#endElement
+ */
+ public void endElement (String name)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of character data inside an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method to take specific actions for each chunk of character data
+ * (such as adding the data to a node or buffer, or printing it to
+ * a file).</p>
+ *
+ * @param ch The characters.
+ * @param start The start position in the character array.
+ * @param length The number of characters to use from the
+ * character array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#characters
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of ignorable whitespace in element content.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method to take specific actions for each chunk of ignorable
+ * whitespace (such as adding data to a node or buffer, or printing
+ * it to a file).</p>
+ *
+ * @param ch The whitespace characters.
+ * @param start The start position in the character array.
+ * @param length The number of characters to use from the
+ * character array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#ignorableWhitespace
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of a processing instruction.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions for each
+ * processing instruction, such as setting status variables or
+ * invoking other methods.</p>
+ *
+ * @param target The processing instruction target.
+ * @param data The processing instruction data, or null if
+ * none is supplied.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DocumentHandler#processingInstruction
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of the ErrorHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive notification of a parser warning.
+ *
+ * <p>The default implementation does nothing. Application writers
+ * may override this method in a subclass to take specific actions
+ * for each warning, such as inserting the message in a log file or
+ * printing it to the console.</p>
+ *
+ * @param e The warning information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#warning
+ * @see org.xml.sax.SAXParseException
+ */
+ public void warning (SAXParseException e)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of a recoverable parser error.
+ *
+ * <p>The default implementation does nothing. Application writers
+ * may override this method in a subclass to take specific actions
+ * for each error, such as inserting the message in a log file or
+ * printing it to the console.</p>
+ *
+ * @param e The warning information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#warning
+ * @see org.xml.sax.SAXParseException
+ */
+ public void error (SAXParseException e)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Report a fatal XML parsing error.
+ *
+ * <p>The default implementation throws a SAXParseException.
+ * Application writers may override this method in a subclass if
+ * they need to take specific actions for each fatal error (such as
+ * collecting all of the errors into a single report): in any case,
+ * the application must stop all regular processing when this
+ * method is invoked, since the document is no longer reliable, and
+ * the parser may no longer report parsing events.</p>
+ *
+ * @param e The error information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#fatalError
+ * @see org.xml.sax.SAXParseException
+ */
+ public void fatalError (SAXParseException e)
+ throws SAXException
+ {
+ throw e;
+ }
+
+}
+
+// end of HandlerBase.java
diff --git a/src/org/xml/sax/InputSource.java b/src/org/xml/sax/InputSource.java
new file mode 100644
index 0000000..aff5806
--- /dev/null
+++ b/src/org/xml/sax/InputSource.java
@@ -0,0 +1,336 @@
+// SAX input source.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: InputSource.java,v 1.9 2002/01/30 21:13:45 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.Reader;
+import java.io.InputStream;
+
+/**
+ * A single input source for an XML entity.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class allows a SAX application to encapsulate information
+ * about an input source in a single object, which may include
+ * a public identifier, a system identifier, a byte stream (possibly
+ * with a specified encoding), and/or a character stream.</p>
+ *
+ * <p>There are two places that the application can deliver an
+ * input source to the parser: as the argument to the Parser.parse
+ * method, or as the return value of the EntityResolver.resolveEntity
+ * method.</p>
+ *
+ * <p>The SAX parser will use the InputSource object to determine how
+ * to read XML input. If there is a character stream available, the
+ * parser will read that stream directly, disregarding any text
+ * encoding declaration found in that stream.
+ * If there is no character stream, but there is
+ * a byte stream, the parser will use that byte stream, using the
+ * encoding specified in the InputSource or else (if no encoding is
+ * specified) autodetecting the character encoding using an algorithm
+ * such as the one in the XML specification. If neither a character
+ * stream nor a
+ * byte stream is available, the parser will attempt to open a URI
+ * connection to the resource identified by the system
+ * identifier.</p>
+ *
+ * <p>An InputSource object belongs to the application: the SAX parser
+ * shall never modify it in any way (it may modify a copy if
+ * necessary). However, standard processing of both byte and
+ * character streams is to close them on as part of end-of-parse cleanup,
+ * so applications should not attempt to re-use such streams after they
+ * have been handed to a parser. </p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource)
+ * @see org.xml.sax.EntityResolver#resolveEntity
+ * @see java.io.InputStream
+ * @see java.io.Reader
+ */
+public class InputSource {
+
+ /**
+ * Zero-argument default constructor.
+ *
+ * @see #setPublicId
+ * @see #setSystemId
+ * @see #setByteStream
+ * @see #setCharacterStream
+ * @see #setEncoding
+ */
+ public InputSource ()
+ {
+ }
+
+
+ /**
+ * Create a new input source with a system identifier.
+ *
+ * <p>Applications may use setPublicId to include a
+ * public identifier as well, or setEncoding to specify
+ * the character encoding, if known.</p>
+ *
+ * <p>If the system identifier is a URL, it must be fully
+ * resolved (it may not be a relative URL).</p>
+ *
+ * @param systemId The system identifier (URI).
+ * @see #setPublicId
+ * @see #setSystemId
+ * @see #setByteStream
+ * @see #setEncoding
+ * @see #setCharacterStream
+ */
+ public InputSource (String systemId)
+ {
+ setSystemId(systemId);
+ }
+
+
+ /**
+ * Create a new input source with a byte stream.
+ *
+ * <p>Application writers should use setSystemId() to provide a base
+ * for resolving relative URIs, may use setPublicId to include a
+ * public identifier, and may use setEncoding to specify the object's
+ * character encoding.</p>
+ *
+ * @param byteStream The raw byte stream containing the document.
+ * @see #setPublicId
+ * @see #setSystemId
+ * @see #setEncoding
+ * @see #setByteStream
+ * @see #setCharacterStream
+ */
+ public InputSource (InputStream byteStream)
+ {
+ setByteStream(byteStream);
+ }
+
+
+ /**
+ * Create a new input source with a character stream.
+ *
+ * <p>Application writers should use setSystemId() to provide a base
+ * for resolving relative URIs, and may use setPublicId to include a
+ * public identifier.</p>
+ *
+ * <p>The character stream shall not include a byte order mark.</p>
+ *
+ * @see #setPublicId
+ * @see #setSystemId
+ * @see #setByteStream
+ * @see #setCharacterStream
+ */
+ public InputSource (Reader characterStream)
+ {
+ setCharacterStream(characterStream);
+ }
+
+
+ /**
+ * Set the public identifier for this input source.
+ *
+ * <p>The public identifier is always optional: if the application
+ * writer includes one, it will be provided as part of the
+ * location information.</p>
+ *
+ * @param publicId The public identifier as a string.
+ * @see #getPublicId
+ * @see org.xml.sax.Locator#getPublicId
+ * @see org.xml.sax.SAXParseException#getPublicId
+ */
+ public void setPublicId (String publicId)
+ {
+ this.publicId = publicId;
+ }
+
+
+ /**
+ * Get the public identifier for this input source.
+ *
+ * @return The public identifier, or null if none was supplied.
+ * @see #setPublicId
+ */
+ public String getPublicId ()
+ {
+ return publicId;
+ }
+
+
+ /**
+ * Set the system identifier for this input source.
+ *
+ * <p>The system identifier is optional if there is a byte stream
+ * or a character stream, but it is still useful to provide one,
+ * since the application can use it to resolve relative URIs
+ * and can include it in error messages and warnings (the parser
+ * will attempt to open a connection to the URI only if
+ * there is no byte stream or character stream specified).</p>
+ *
+ * <p>If the application knows the character encoding of the
+ * object pointed to by the system identifier, it can register
+ * the encoding using the setEncoding method.</p>
+ *
+ * <p>If the system identifier is a URL, it must be fully
+ * resolved (it may not be a relative URL).</p>
+ *
+ * @param systemId The system identifier as a string.
+ * @see #setEncoding
+ * @see #getSystemId
+ * @see org.xml.sax.Locator#getSystemId
+ * @see org.xml.sax.SAXParseException#getSystemId
+ */
+ public void setSystemId (String systemId)
+ {
+ this.systemId = systemId;
+ }
+
+
+ /**
+ * Get the system identifier for this input source.
+ *
+ * <p>The getEncoding method will return the character encoding
+ * of the object pointed to, or null if unknown.</p>
+ *
+ * <p>If the system ID is a URL, it will be fully resolved.</p>
+ *
+ * @return The system identifier, or null if none was supplied.
+ * @see #setSystemId
+ * @see #getEncoding
+ */
+ public String getSystemId ()
+ {
+ return systemId;
+ }
+
+
+ /**
+ * Set the byte stream for this input source.
+ *
+ * <p>The SAX parser will ignore this if there is also a character
+ * stream specified, but it will use a byte stream in preference
+ * to opening a URI connection itself.</p>
+ *
+ * <p>If the application knows the character encoding of the
+ * byte stream, it should set it with the setEncoding method.</p>
+ *
+ * @param byteStream A byte stream containing an XML document or
+ * other entity.
+ * @see #setEncoding
+ * @see #getByteStream
+ * @see #getEncoding
+ * @see java.io.InputStream
+ */
+ public void setByteStream (InputStream byteStream)
+ {
+ this.byteStream = byteStream;
+ }
+
+
+ /**
+ * Get the byte stream for this input source.
+ *
+ * <p>The getEncoding method will return the character
+ * encoding for this byte stream, or null if unknown.</p>
+ *
+ * @return The byte stream, or null if none was supplied.
+ * @see #getEncoding
+ * @see #setByteStream
+ */
+ public InputStream getByteStream ()
+ {
+ return byteStream;
+ }
+
+
+ /**
+ * Set the character encoding, if known.
+ *
+ * <p>The encoding must be a string acceptable for an
+ * XML encoding declaration (see section 4.3.3 of the XML 1.0
+ * recommendation).</p>
+ *
+ * <p>This method has no effect when the application provides a
+ * character stream.</p>
+ *
+ * @param encoding A string describing the character encoding.
+ * @see #setSystemId
+ * @see #setByteStream
+ * @see #getEncoding
+ */
+ public void setEncoding (String encoding)
+ {
+ this.encoding = encoding;
+ }
+
+
+ /**
+ * Get the character encoding for a byte stream or URI.
+ * This value will be ignored when the application provides a
+ * character stream.
+ *
+ * @return The encoding, or null if none was supplied.
+ * @see #setByteStream
+ * @see #getSystemId
+ * @see #getByteStream
+ */
+ public String getEncoding ()
+ {
+ return encoding;
+ }
+
+
+ /**
+ * Set the character stream for this input source.
+ *
+ * <p>If there is a character stream specified, the SAX parser
+ * will ignore any byte stream and will not attempt to open
+ * a URI connection to the system identifier.</p>
+ *
+ * @param characterStream The character stream containing the
+ * XML document or other entity.
+ * @see #getCharacterStream
+ * @see java.io.Reader
+ */
+ public void setCharacterStream (Reader characterStream)
+ {
+ this.characterStream = characterStream;
+ }
+
+
+ /**
+ * Get the character stream for this input source.
+ *
+ * @return The character stream, or null if none was supplied.
+ * @see #setCharacterStream
+ */
+ public Reader getCharacterStream ()
+ {
+ return characterStream;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ private String publicId;
+ private String systemId;
+ private InputStream byteStream;
+ private String encoding;
+ private Reader characterStream;
+
+}
+
+// end of InputSource.java
diff --git a/src/org/xml/sax/Locator.java b/src/org/xml/sax/Locator.java
new file mode 100644
index 0000000..f8f3484
--- /dev/null
+++ b/src/org/xml/sax/Locator.java
@@ -0,0 +1,136 @@
+// SAX locator interface for document events.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: Locator.java,v 1.8 2002/01/30 21:13:47 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for associating a SAX event with a document location.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX parser provides location information to the SAX
+ * application, it does so by implementing this interface and then
+ * passing an instance to the application using the content
+ * handler's {@link org.xml.sax.ContentHandler#setDocumentLocator
+ * setDocumentLocator} method. The application can use the
+ * object to obtain the location of any other SAX event
+ * in the XML source document.</p>
+ *
+ * <p>Note that the results returned by the object will be valid only
+ * during the scope of each callback method: the application
+ * will receive unpredictable results if it attempts to use the
+ * locator at any other time, or after parsing completes.</p>
+ *
+ * <p>SAX parsers are not required to supply a locator, but they are
+ * very strongly encouraged to do so. If the parser supplies a
+ * locator, it must do so before reporting any other document events.
+ * If no locator has been set by the time the application receives
+ * the {@link org.xml.sax.ContentHandler#startDocument startDocument}
+ * event, the application should assume that a locator is not
+ * available.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.ContentHandler#setDocumentLocator
+ */
+public interface Locator {
+
+
+ /**
+ * Return the public identifier for the current document event.
+ *
+ * <p>The return value is the public identifier of the document
+ * entity or of the external parsed entity in which the markup
+ * triggering the event appears.</p>
+ *
+ * @return A string containing the public identifier, or
+ * null if none is available.
+ * @see #getSystemId
+ */
+ public abstract String getPublicId ();
+
+
+ /**
+ * Return the system identifier for the current document event.
+ *
+ * <p>The return value is the system identifier of the document
+ * entity or of the external parsed entity in which the markup
+ * triggering the event appears.</p>
+ *
+ * <p>If the system identifier is a URL, the parser must resolve it
+ * fully before passing it to the application. For example, a file
+ * name must always be provided as a <em>file:...</em> URL, and other
+ * kinds of relative URI are also resolved against their bases.</p>
+ *
+ * @return A string containing the system identifier, or null
+ * if none is available.
+ * @see #getPublicId
+ */
+ public abstract String getSystemId ();
+
+
+ /**
+ * Return the line number where the current document event ends.
+ * Lines are delimited by line ends, which are defined in
+ * the XML specification.
+ *
+ * <p><strong>Warning:</strong> The return value from the method
+ * is intended only as an approximation for the sake of diagnostics;
+ * it is not intended to provide sufficient information
+ * to edit the character content of the original XML document.
+ * In some cases, these "line" numbers match what would be displayed
+ * as columns, and in others they may not match the source text
+ * due to internal entity expansion. </p>
+ *
+ * <p>The return value is an approximation of the line number
+ * in the document entity or external parsed entity where the
+ * markup triggering the event appears.</p>
+ *
+ * <p>If possible, the SAX driver should provide the line position
+ * of the first character after the text associated with the document
+ * event. The first line is line 1.</p>
+ *
+ * @return The line number, or -1 if none is available.
+ * @see #getColumnNumber
+ */
+ public abstract int getLineNumber ();
+
+
+ /**
+ * Return the column number where the current document event ends.
+ * This is one-based number of Java <code>char</code> values since
+ * the last line end.
+ *
+ * <p><strong>Warning:</strong> The return value from the method
+ * is intended only as an approximation for the sake of diagnostics;
+ * it is not intended to provide sufficient information
+ * to edit the character content of the original XML document.
+ * For example, when lines contain combining character sequences, wide
+ * characters, surrogate pairs, or bi-directional text, the value may
+ * not correspond to the column in a text editor's display. </p>
+ *
+ * <p>The return value is an approximation of the column number
+ * in the document entity or external parsed entity where the
+ * markup triggering the event appears.</p>
+ *
+ * <p>If possible, the SAX driver should provide the line position
+ * of the first character after the text associated with the document
+ * event. The first column in each line is column 1.</p>
+ *
+ * @return The column number, or -1 if none is available.
+ * @see #getLineNumber
+ */
+ public abstract int getColumnNumber ();
+
+}
+
+// end of Locator.java
diff --git a/src/org/xml/sax/Parser.java b/src/org/xml/sax/Parser.java
new file mode 100644
index 0000000..734819d
--- /dev/null
+++ b/src/org/xml/sax/Parser.java
@@ -0,0 +1,209 @@
+// SAX parser interface.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: Parser.java,v 1.6 2002/01/30 21:13:47 dbrownell Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+import java.util.Locale;
+
+
+/**
+ * Basic interface for SAX (Simple API for XML) parsers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This was the main event supplier interface for SAX1; it has
+ * been replaced in SAX2 by {@link org.xml.sax.XMLReader XMLReader},
+ * which includes Namespace support and sophisticated configurability
+ * and extensibility.</p>
+ *
+ * <p>All SAX1 parsers must implement this basic interface: it allows
+ * applications to register handlers for different types of events
+ * and to initiate a parse from a URI, or a character stream.</p>
+ *
+ * <p>All SAX1 parsers must also implement a zero-argument constructor
+ * (though other constructors are also allowed).</p>
+ *
+ * <p>SAX1 parsers are reusable but not re-entrant: the application
+ * may reuse a parser object (possibly with a different input source)
+ * once the first parse has completed successfully, but it may not
+ * invoke the parse() methods recursively within a parse.</p>
+ *
+ * @deprecated This interface has been replaced by the SAX2
+ * {@link org.xml.sax.XMLReader XMLReader}
+ * interface, which includes Namespace support.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.DocumentHandler
+ * @see org.xml.sax.ErrorHandler
+ * @see org.xml.sax.HandlerBase
+ * @see org.xml.sax.InputSource
+ */
+public interface Parser
+{
+
+ /**
+ * Allow an application to request a locale for errors and warnings.
+ *
+ * <p>SAX parsers are not required to provide localisation for errors
+ * and warnings; if they cannot support the requested locale,
+ * however, they must throw a SAX exception. Applications may
+ * not request a locale change in the middle of a parse.</p>
+ *
+ * @param locale A Java Locale object.
+ * @exception org.xml.sax.SAXException Throws an exception
+ * (using the previous or default locale) if the
+ * requested locale is not supported.
+ * @see org.xml.sax.SAXException
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void setLocale (Locale locale)
+ throws SAXException;
+
+
+ /**
+ * Allow an application to register a custom entity resolver.
+ *
+ * <p>If the application does not register an entity resolver, the
+ * SAX parser will resolve system identifiers and open connections
+ * to entities itself (this is the default behaviour implemented in
+ * HandlerBase).</p>
+ *
+ * <p>Applications may register a new or different entity resolver
+ * in the middle of a parse, and the SAX parser must begin using
+ * the new resolver immediately.</p>
+ *
+ * @param resolver The object for resolving entities.
+ * @see EntityResolver
+ * @see HandlerBase
+ */
+ public abstract void setEntityResolver (EntityResolver resolver);
+
+
+ /**
+ * Allow an application to register a DTD event handler.
+ *
+ * <p>If the application does not register a DTD handler, all DTD
+ * events reported by the SAX parser will be silently
+ * ignored (this is the default behaviour implemented by
+ * HandlerBase).</p>
+ *
+ * <p>Applications may register a new or different
+ * handler in the middle of a parse, and the SAX parser must
+ * begin using the new handler immediately.</p>
+ *
+ * @param handler The DTD handler.
+ * @see DTDHandler
+ * @see HandlerBase
+ */
+ public abstract void setDTDHandler (DTDHandler handler);
+
+
+ /**
+ * Allow an application to register a document event handler.
+ *
+ * <p>If the application does not register a document handler, all
+ * document events reported by the SAX parser will be silently
+ * ignored (this is the default behaviour implemented by
+ * HandlerBase).</p>
+ *
+ * <p>Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.</p>
+ *
+ * @param handler The document handler.
+ * @see DocumentHandler
+ * @see HandlerBase
+ */
+ public abstract void setDocumentHandler (DocumentHandler handler);
+
+
+ /**
+ * Allow an application to register an error event handler.
+ *
+ * <p>If the application does not register an error event handler,
+ * all error events reported by the SAX parser will be silently
+ * ignored, except for fatalError, which will throw a SAXException
+ * (this is the default behaviour implemented by HandlerBase).</p>
+ *
+ * <p>Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.</p>
+ *
+ * @param handler The error handler.
+ * @see ErrorHandler
+ * @see SAXException
+ * @see HandlerBase
+ */
+ public abstract void setErrorHandler (ErrorHandler handler);
+
+
+ /**
+ * Parse an XML document.
+ *
+ * <p>The application can use this method to instruct the SAX parser
+ * to begin parsing an XML document from any valid input
+ * source (a character stream, a byte stream, or a URI).</p>
+ *
+ * <p>Applications may not invoke this method while a parse is in
+ * progress (they should create a new Parser instead for each
+ * additional XML document). Once a parse is complete, an
+ * application may reuse the same Parser object, possibly with a
+ * different input source.</p>
+ *
+ * @param source The input source for the top-level of the
+ * XML document.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ * @see org.xml.sax.InputSource
+ * @see #parse(java.lang.String)
+ * @see #setEntityResolver
+ * @see #setDTDHandler
+ * @see #setDocumentHandler
+ * @see #setErrorHandler
+ */
+ public abstract void parse (InputSource source)
+ throws SAXException, IOException;
+
+
+ /**
+ * Parse an XML document from a system identifier (URI).
+ *
+ * <p>This method is a shortcut for the common case of reading a
+ * document from a system identifier. It is the exact
+ * equivalent of the following:</p>
+ *
+ * <pre>
+ * parse(new InputSource(systemId));
+ * </pre>
+ *
+ * <p>If the system identifier is a URL, it must be fully resolved
+ * by the application before it is passed to the parser.</p>
+ *
+ * @param systemId The system identifier (URI).
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ * @see #parse(org.xml.sax.InputSource)
+ */
+ public abstract void parse (String systemId)
+ throws SAXException, IOException;
+
+}
+
+// end of Parser.java
diff --git a/src/org/xml/sax/SAXException.java b/src/org/xml/sax/SAXException.java
new file mode 100644
index 0000000..256719c
--- /dev/null
+++ b/src/org/xml/sax/SAXException.java
@@ -0,0 +1,153 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate a general SAX error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class can contain basic error or warning information from
+ * either the XML parser or the application: a parser writer or
+ * application writer can subclass it to provide additional
+ * functionality. SAX handlers may throw this exception or
+ * any exception subclassed from it.</p>
+ *
+ * <p>If the application needs to pass through other types of
+ * exceptions, it must wrap those exceptions in a SAXException
+ * or an exception derived from a SAXException.</p>
+ *
+ * <p>If the parser or application needs to include information about a
+ * specific location in an XML document, it should use the
+ * {@link org.xml.sax.SAXParseException SAXParseException} subclass.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXParseException
+ */
+public class SAXException extends Exception {
+
+
+ /**
+ * Create a new SAXException.
+ */
+ public SAXException ()
+ {
+ super();
+ this.exception = null;
+ }
+
+
+ /**
+ * Create a new SAXException.
+ *
+ * @param message The error or warning message.
+ */
+ public SAXException (String message) {
+ super(message);
+ this.exception = null;
+ }
+
+
+ /**
+ * Create a new SAXException wrapping an existing exception.
+ *
+ * <p>The existing exception will be embedded in the new
+ * one, and its message will become the default message for
+ * the SAXException.</p>
+ *
+ * @param e The exception to be wrapped in a SAXException.
+ */
+ public SAXException (Exception e)
+ {
+ super();
+ this.exception = e;
+ }
+
+
+ /**
+ * Create a new SAXException from an existing exception.
+ *
+ * <p>The existing exception will be embedded in the new
+ * one, but the new exception will have its own message.</p>
+ *
+ * @param message The detail message.
+ * @param e The exception to be wrapped in a SAXException.
+ */
+ public SAXException (String message, Exception e)
+ {
+ super(message);
+ this.exception = e;
+ }
+
+
+ /**
+ * Return a detail message for this exception.
+ *
+ * <p>If there is an embedded exception, and if the SAXException
+ * has no detail message of its own, this method will return
+ * the detail message from the embedded exception.</p>
+ *
+ * @return The error or warning message.
+ */
+ public String getMessage ()
+ {
+ String message = super.getMessage();
+
+ if (message == null && exception != null) {
+ return exception.getMessage();
+ } else {
+ return message;
+ }
+ }
+
+
+ /**
+ * Return the embedded exception, if any.
+ *
+ * @return The embedded exception, or null if there is none.
+ */
+ public Exception getException ()
+ {
+ return exception;
+ }
+
+
+ /**
+ * Override toString to pick up any embedded exception.
+ *
+ * @return A string representation of this exception.
+ */
+ public String toString ()
+ {
+ if (exception != null) {
+ return exception.toString();
+ } else {
+ return super.toString();
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Internal state.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * @serial The embedded exception if tunnelling, or null.
+ */
+ private Exception exception;
+
+}
+
+// end of SAXException.java
diff --git a/src/org/xml/sax/SAXNotRecognizedException.java b/src/org/xml/sax/SAXNotRecognizedException.java
new file mode 100644
index 0000000..0bb1ded
--- /dev/null
+++ b/src/org/xml/sax/SAXNotRecognizedException.java
@@ -0,0 +1,53 @@
+// SAXNotRecognizedException.java - unrecognized feature or value.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: SAXNotRecognizedException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Exception class for an unrecognized identifier.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XMLReader will throw this exception when it finds an
+ * unrecognized feature or property identifier; SAX applications and
+ * extensions may use this class for other, similar purposes.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXNotSupportedException
+ */
+public class SAXNotRecognizedException extends SAXException
+{
+
+ /**
+ * Default constructor.
+ */
+ public SAXNotRecognizedException ()
+ {
+ super();
+ }
+
+
+ /**
+ * Construct a new exception with the given message.
+ *
+ * @param message The text message of the exception.
+ */
+ public SAXNotRecognizedException (String message)
+ {
+ super(message);
+ }
+
+}
+
+// end of SAXNotRecognizedException.java
diff --git a/src/org/xml/sax/SAXNotSupportedException.java b/src/org/xml/sax/SAXNotSupportedException.java
new file mode 100644
index 0000000..9a645a9
--- /dev/null
+++ b/src/org/xml/sax/SAXNotSupportedException.java
@@ -0,0 +1,53 @@
+// SAXNotSupportedException.java - unsupported feature or value.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: SAXNotSupportedException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Exception class for an unsupported operation.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XMLReader will throw this exception when it recognizes a
+ * feature or property identifier, but cannot perform the requested
+ * operation (setting a state or value). Other SAX2 applications and
+ * extensions may use this class for similar purposes.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXNotRecognizedException
+ */
+public class SAXNotSupportedException extends SAXException
+{
+
+ /**
+ * Construct a new exception with no message.
+ */
+ public SAXNotSupportedException ()
+ {
+ super();
+ }
+
+
+ /**
+ * Construct a new exception with the given message.
+ *
+ * @param message The text message of the exception.
+ */
+ public SAXNotSupportedException (String message)
+ {
+ super(message);
+ }
+
+}
+
+// end of SAXNotSupportedException.java
diff --git a/src/org/xml/sax/SAXParseException.java b/src/org/xml/sax/SAXParseException.java
new file mode 100644
index 0000000..1df5e14
--- /dev/null
+++ b/src/org/xml/sax/SAXParseException.java
@@ -0,0 +1,269 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXParseException.java,v 1.11 2004/04/21 13:05:02 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate an XML parse error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This exception may include information for locating the error
+ * in the original XML document, as if it came from a {@link Locator}
+ * object. Note that although the application
+ * will receive a SAXParseException as the argument to the handlers
+ * in the {@link org.xml.sax.ErrorHandler ErrorHandler} interface,
+ * the application is not actually required to throw the exception;
+ * instead, it can simply read the information in it and take a
+ * different action.</p>
+ *
+ * <p>Since this exception is a subclass of {@link org.xml.sax.SAXException
+ * SAXException}, it inherits the ability to wrap another exception.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXException
+ * @see org.xml.sax.Locator
+ * @see org.xml.sax.ErrorHandler
+ */
+public class SAXParseException extends SAXException {
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Constructors.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Create a new SAXParseException from a message and a Locator.
+ *
+ * <p>This constructor is especially useful when an application is
+ * creating its own exception from within a {@link org.xml.sax.ContentHandler
+ * ContentHandler} callback.</p>
+ *
+ * @param message The error or warning message.
+ * @param locator The locator object for the error or warning (may be
+ * null).
+ * @see org.xml.sax.Locator
+ */
+ public SAXParseException (String message, Locator locator) {
+ super(message);
+ if (locator != null) {
+ init(locator.getPublicId(), locator.getSystemId(),
+ locator.getLineNumber(), locator.getColumnNumber());
+ } else {
+ init(null, null, -1, -1);
+ }
+ }
+
+
+ /**
+ * Wrap an existing exception in a SAXParseException.
+ *
+ * <p>This constructor is especially useful when an application is
+ * creating its own exception from within a {@link org.xml.sax.ContentHandler
+ * ContentHandler} callback, and needs to wrap an existing exception that is not a
+ * subclass of {@link org.xml.sax.SAXException SAXException}.</p>
+ *
+ * @param message The error or warning message, or null to
+ * use the message from the embedded exception.
+ * @param locator The locator object for the error or warning (may be
+ * null).
+ * @param e Any exception.
+ * @see org.xml.sax.Locator
+ */
+ public SAXParseException (String message, Locator locator,
+ Exception e) {
+ super(message, e);
+ if (locator != null) {
+ init(locator.getPublicId(), locator.getSystemId(),
+ locator.getLineNumber(), locator.getColumnNumber());
+ } else {
+ init(null, null, -1, -1);
+ }
+ }
+
+
+ /**
+ * Create a new SAXParseException.
+ *
+ * <p>This constructor is most useful for parser writers.</p>
+ *
+ * <p>All parameters except the message are as if
+ * they were provided by a {@link Locator}. For example, if the
+ * system identifier is a URL (including relative filename), the
+ * caller must resolve it fully before creating the exception.</p>
+ *
+ *
+ * @param message The error or warning message.
+ * @param publicId The public identifier of the entity that generated
+ * the error or warning.
+ * @param systemId The system identifier of the entity that generated
+ * the error or warning.
+ * @param lineNumber The line number of the end of the text that
+ * caused the error or warning.
+ * @param columnNumber The column number of the end of the text that
+ * cause the error or warning.
+ */
+ public SAXParseException (String message, String publicId, String systemId,
+ int lineNumber, int columnNumber)
+ {
+ super(message);
+ init(publicId, systemId, lineNumber, columnNumber);
+ }
+
+
+ /**
+ * Create a new SAXParseException with an embedded exception.
+ *
+ * <p>This constructor is most useful for parser writers who
+ * need to wrap an exception that is not a subclass of
+ * {@link org.xml.sax.SAXException SAXException}.</p>
+ *
+ * <p>All parameters except the message and exception are as if
+ * they were provided by a {@link Locator}. For example, if the
+ * system identifier is a URL (including relative filename), the
+ * caller must resolve it fully before creating the exception.</p>
+ *
+ * @param message The error or warning message, or null to use
+ * the message from the embedded exception.
+ * @param publicId The public identifier of the entity that generated
+ * the error or warning.
+ * @param systemId The system identifier of the entity that generated
+ * the error or warning.
+ * @param lineNumber The line number of the end of the text that
+ * caused the error or warning.
+ * @param columnNumber The column number of the end of the text that
+ * cause the error or warning.
+ * @param e Another exception to embed in this one.
+ */
+ public SAXParseException (String message, String publicId, String systemId,
+ int lineNumber, int columnNumber, Exception e)
+ {
+ super(message, e);
+ init(publicId, systemId, lineNumber, columnNumber);
+ }
+
+
+ /**
+ * Internal initialization method.
+ *
+ * @param publicId The public identifier of the entity which generated the exception,
+ * or null.
+ * @param systemId The system identifier of the entity which generated the exception,
+ * or null.
+ * @param lineNumber The line number of the error, or -1.
+ * @param columnNumber The column number of the error, or -1.
+ */
+ private void init (String publicId, String systemId,
+ int lineNumber, int columnNumber)
+ {
+ this.publicId = publicId;
+ this.systemId = systemId;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ }
+
+
+ /**
+ * Get the public identifier of the entity where the exception occurred.
+ *
+ * @return A string containing the public identifier, or null
+ * if none is available.
+ * @see org.xml.sax.Locator#getPublicId
+ */
+ public String getPublicId ()
+ {
+ return this.publicId;
+ }
+
+
+ /**
+ * Get the system identifier of the entity where the exception occurred.
+ *
+ * <p>If the system identifier is a URL, it will have been resolved
+ * fully.</p>
+ *
+ * @return A string containing the system identifier, or null
+ * if none is available.
+ * @see org.xml.sax.Locator#getSystemId
+ */
+ public String getSystemId ()
+ {
+ return this.systemId;
+ }
+
+
+ /**
+ * The line number of the end of the text where the exception occurred.
+ *
+ * <p>The first line is line 1.</p>
+ *
+ * @return An integer representing the line number, or -1
+ * if none is available.
+ * @see org.xml.sax.Locator#getLineNumber
+ */
+ public int getLineNumber ()
+ {
+ return this.lineNumber;
+ }
+
+
+ /**
+ * The column number of the end of the text where the exception occurred.
+ *
+ * <p>The first column in a line is position 1.</p>
+ *
+ * @return An integer representing the column number, or -1
+ * if none is available.
+ * @see org.xml.sax.Locator#getColumnNumber
+ */
+ public int getColumnNumber ()
+ {
+ return this.columnNumber;
+ }
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Internal state.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * @serial The public identifier, or null.
+ * @see #getPublicId
+ */
+ private String publicId;
+
+
+ /**
+ * @serial The system identifier, or null.
+ * @see #getSystemId
+ */
+ private String systemId;
+
+
+ /**
+ * @serial The line number, or -1.
+ * @see #getLineNumber
+ */
+ private int lineNumber;
+
+
+ /**
+ * @serial The column number, or -1.
+ * @see #getColumnNumber
+ */
+ private int columnNumber;
+
+}
+
+// end of SAXParseException.java
diff --git a/src/org/xml/sax/XMLFilter.java b/src/org/xml/sax/XMLFilter.java
new file mode 100644
index 0000000..5a399fa
--- /dev/null
+++ b/src/org/xml/sax/XMLFilter.java
@@ -0,0 +1,65 @@
+// XMLFilter.java - filter SAX2 events.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: XMLFilter.java,v 1.6 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for an XML filter.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>An XML filter is like an XML reader, except that it obtains its
+ * events from another XML reader rather than a primary source like
+ * an XML document or database. Filters can modify a stream of
+ * events as they pass on to the final application.</p>
+ *
+ * <p>The XMLFilterImpl helper class provides a convenient base
+ * for creating SAX2 filters, by passing on all {@link org.xml.sax.EntityResolver
+ * EntityResolver}, {@link org.xml.sax.DTDHandler DTDHandler},
+ * {@link org.xml.sax.ContentHandler ContentHandler} and {@link org.xml.sax.ErrorHandler
+ * ErrorHandler} events automatically.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.XMLFilterImpl
+ */
+public interface XMLFilter extends XMLReader
+{
+
+ /**
+ * Set the parent reader.
+ *
+ * <p>This method allows the application to link the filter to
+ * a parent reader (which may be another filter). The argument
+ * may not be null.</p>
+ *
+ * @param parent The parent reader.
+ */
+ public abstract void setParent (XMLReader parent);
+
+
+ /**
+ * Get the parent reader.
+ *
+ * <p>This method allows the application to query the parent
+ * reader (which may be another filter). It is generally a
+ * bad idea to perform any operations on the parent reader
+ * directly: they should all pass through this filter.</p>
+ *
+ * @return The parent filter, or null if none has been set.
+ */
+ public abstract XMLReader getParent ();
+
+}
+
+// end of XMLFilter.java
diff --git a/src/org/xml/sax/XMLReader.java b/src/org/xml/sax/XMLReader.java
new file mode 100644
index 0000000..9c150a0
--- /dev/null
+++ b/src/org/xml/sax/XMLReader.java
@@ -0,0 +1,404 @@
+// XMLReader.java - read an XML document.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: XMLReader.java,v 1.9 2004/04/26 17:34:34 dmegginson Exp $
+
+package org.xml.sax;
+
+import java.io.IOException;
+
+
+/**
+ * Interface for reading an XML document using callbacks.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p><strong>Note:</strong> despite its name, this interface does
+ * <em>not</em> extend the standard Java {@link java.io.Reader Reader}
+ * interface, because reading XML is a fundamentally different activity
+ * than reading character data.</p>
+ *
+ * <p>XMLReader is the interface that an XML parser's SAX2 driver must
+ * implement. This interface allows an application to set and
+ * query features and properties in the parser, to register
+ * event handlers for document processing, and to initiate
+ * a document parse.</p>
+ *
+ * <p>All SAX interfaces are assumed to be synchronous: the
+ * {@link #parse parse} methods must not return until parsing
+ * is complete, and readers must wait for an event-handler callback
+ * to return before reporting the next event.</p>
+ *
+ * <p>This interface replaces the (now deprecated) SAX 1.0 {@link
+ * org.xml.sax.Parser Parser} interface. The XMLReader interface
+ * contains two important enhancements over the old Parser
+ * interface (as well as some minor ones):</p>
+ *
+ * <ol>
+ * <li>it adds a standard way to query and set features and
+ * properties; and</li>
+ * <li>it adds Namespace support, which is required for many
+ * higher-level XML standards.</li>
+ * </ol>
+ *
+ * <p>There are adapters available to convert a SAX1 Parser to
+ * a SAX2 XMLReader and vice-versa.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLFilter
+ * @see org.xml.sax.helpers.ParserAdapter
+ * @see org.xml.sax.helpers.XMLReaderAdapter
+ */
+public interface XMLReader
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Configuration.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Look up the value of a feature flag.
+ *
+ * <p>The feature name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a feature name but
+ * temporarily be unable to return its value.
+ * Some feature values may be available only in specific
+ * contexts, such as before, during, or after a parse.
+ * Also, some feature values may not be programmatically accessible.
+ * (In the case of an adapter for SAX1 {@link Parser}, there is no
+ * implementation-independent way to expose whether the underlying
+ * parser is performing validation, expanding external entities,
+ * and so forth.) </p>
+ *
+ * <p>All XMLReaders are required to recognize the
+ * http://xml.org/sax/features/namespaces and the
+ * http://xml.org/sax/features/namespace-prefixes feature names.</p>
+ *
+ * <p>Typical usage is something like this:</p>
+ *
+ * <pre>
+ * XMLReader r = new MySAXDriver();
+ *
+ * // try to activate validation
+ * try {
+ * r.setFeature("http://xml.org/sax/features/validation", true);
+ * } catch (SAXException e) {
+ * System.err.println("Cannot activate validation.");
+ * }
+ *
+ * // register event handlers
+ * r.setContentHandler(new MyContentHandler());
+ * r.setErrorHandler(new MyErrorHandler());
+ *
+ * // parse the first document
+ * try {
+ * r.parse("http://www.foo.com/mydoc.xml");
+ * } catch (IOException e) {
+ * System.err.println("I/O exception reading XML document");
+ * } catch (SAXException e) {
+ * System.err.println("XML exception reading document.");
+ * }
+ * </pre>
+ *
+ * <p>Implementors are free (and encouraged) to invent their own features,
+ * using names built on their own URIs.</p>
+ *
+ * @param name The feature name, which is a fully-qualified URI.
+ * @return The current value of the feature (true or false).
+ * @exception org.xml.sax.SAXNotRecognizedException If the feature
+ * value can't be assigned or retrieved.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * XMLReader recognizes the feature name but
+ * cannot determine its value at this time.
+ * @see #setFeature
+ */
+ public boolean getFeature (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+ /**
+ * Set the value of a feature flag.
+ *
+ * <p>The feature name is any fully-qualified URI. It is
+ * possible for an XMLReader to expose a feature value but
+ * to be unable to change the current value.
+ * Some feature values may be immutable or mutable only
+ * in specific contexts, such as before, during, or after
+ * a parse.</p>
+ *
+ * <p>All XMLReaders are required to support setting
+ * http://xml.org/sax/features/namespaces to true and
+ * http://xml.org/sax/features/namespace-prefixes to false.</p>
+ *
+ * @param name The feature name, which is a fully-qualified URI.
+ * @param value The requested value of the feature (true or false).
+ * @exception org.xml.sax.SAXNotRecognizedException If the feature
+ * value can't be assigned or retrieved.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * XMLReader recognizes the feature name but
+ * cannot set the requested value.
+ * @see #getFeature
+ */
+ public void setFeature (String name, boolean value)
+ throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+ /**
+ * Look up the value of a property.
+ *
+ * <p>The property name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a property name but
+ * temporarily be unable to return its value.
+ * Some property values may be available only in specific
+ * contexts, such as before, during, or after a parse.</p>
+ *
+ * <p>XMLReaders are not required to recognize any specific
+ * property names, though an initial core set is documented for
+ * SAX2.</p>
+ *
+ * <p>Implementors are free (and encouraged) to invent their own properties,
+ * using names built on their own URIs.</p>
+ *
+ * @param name The property name, which is a fully-qualified URI.
+ * @return The current value of the property.
+ * @exception org.xml.sax.SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * XMLReader recognizes the property name but
+ * cannot determine its value at this time.
+ * @see #setProperty
+ */
+ public Object getProperty (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+ /**
+ * Set the value of a property.
+ *
+ * <p>The property name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a property name but
+ * to be unable to change the current value.
+ * Some property values may be immutable or mutable only
+ * in specific contexts, such as before, during, or after
+ * a parse.</p>
+ *
+ * <p>XMLReaders are not required to recognize setting
+ * any specific property names, though a core set is defined by
+ * SAX2.</p>
+ *
+ * <p>This method is also the standard mechanism for setting
+ * extended handlers.</p>
+ *
+ * @param name The property name, which is a fully-qualified URI.
+ * @param value The requested value for the property.
+ * @exception org.xml.sax.SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * XMLReader recognizes the property name but
+ * cannot set the requested value.
+ */
+ public void setProperty (String name, Object value)
+ throws SAXNotRecognizedException, SAXNotSupportedException;
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Event handlers.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Allow an application to register an entity resolver.
+ *
+ * <p>If the application does not register an entity resolver,
+ * the XMLReader will perform its own default resolution.</p>
+ *
+ * <p>Applications may register a new or different resolver in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * resolver immediately.</p>
+ *
+ * @param resolver The entity resolver.
+ * @see #getEntityResolver
+ */
+ public void setEntityResolver (EntityResolver resolver);
+
+
+ /**
+ * Return the current entity resolver.
+ *
+ * @return The current entity resolver, or null if none
+ * has been registered.
+ * @see #setEntityResolver
+ */
+ public EntityResolver getEntityResolver ();
+
+
+ /**
+ * Allow an application to register a DTD event handler.
+ *
+ * <p>If the application does not register a DTD handler, all DTD
+ * events reported by the SAX parser will be silently ignored.</p>
+ *
+ * <p>Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.</p>
+ *
+ * @param handler The DTD handler.
+ * @see #getDTDHandler
+ */
+ public void setDTDHandler (DTDHandler handler);
+
+
+ /**
+ * Return the current DTD handler.
+ *
+ * @return The current DTD handler, or null if none
+ * has been registered.
+ * @see #setDTDHandler
+ */
+ public DTDHandler getDTDHandler ();
+
+
+ /**
+ * Allow an application to register a content event handler.
+ *
+ * <p>If the application does not register a content handler, all
+ * content events reported by the SAX parser will be silently
+ * ignored.</p>
+ *
+ * <p>Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.</p>
+ *
+ * @param handler The content handler.
+ * @see #getContentHandler
+ */
+ public void setContentHandler (ContentHandler handler);
+
+
+ /**
+ * Return the current content handler.
+ *
+ * @return The current content handler, or null if none
+ * has been registered.
+ * @see #setContentHandler
+ */
+ public ContentHandler getContentHandler ();
+
+
+ /**
+ * Allow an application to register an error event handler.
+ *
+ * <p>If the application does not register an error handler, all
+ * error events reported by the SAX parser will be silently
+ * ignored; however, normal processing may not continue. It is
+ * highly recommended that all SAX applications implement an
+ * error handler to avoid unexpected bugs.</p>
+ *
+ * <p>Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.</p>
+ *
+ * @param handler The error handler.
+ * @see #getErrorHandler
+ */
+ public void setErrorHandler (ErrorHandler handler);
+
+
+ /**
+ * Return the current error handler.
+ *
+ * @return The current error handler, or null if none
+ * has been registered.
+ * @see #setErrorHandler
+ */
+ public ErrorHandler getErrorHandler ();
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Parsing.
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Parse an XML document.
+ *
+ * <p>The application can use this method to instruct the XML
+ * reader to begin parsing an XML document from any valid input
+ * source (a character stream, a byte stream, or a URI).</p>
+ *
+ * <p>Applications may not invoke this method while a parse is in
+ * progress (they should create a new XMLReader instead for each
+ * nested XML document). Once a parse is complete, an
+ * application may reuse the same XMLReader object, possibly with a
+ * different input source.
+ * Configuration of the XMLReader object (such as handler bindings and
+ * values established for feature flags and properties) is unchanged
+ * by completion of a parse, unless the definition of that aspect of
+ * the configuration explicitly specifies other behavior.
+ * (For example, feature flags or properties exposing
+ * characteristics of the document being parsed.)
+ * </p>
+ *
+ * <p>During the parse, the XMLReader will provide information
+ * about the XML document through the registered event
+ * handlers.</p>
+ *
+ * <p>This method is synchronous: it will not return until parsing
+ * has ended. If a client application wants to terminate
+ * parsing early, it should throw an exception.</p>
+ *
+ * @param input The input source for the top-level of the
+ * XML document.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ * @see org.xml.sax.InputSource
+ * @see #parse(java.lang.String)
+ * @see #setEntityResolver
+ * @see #setDTDHandler
+ * @see #setContentHandler
+ * @see #setErrorHandler
+ */
+ public void parse (InputSource input)
+ throws IOException, SAXException;
+
+
+ /**
+ * Parse an XML document from a system identifier (URI).
+ *
+ * <p>This method is a shortcut for the common case of reading a
+ * document from a system identifier. It is the exact
+ * equivalent of the following:</p>
+ *
+ * <pre>
+ * parse(new InputSource(systemId));
+ * </pre>
+ *
+ * <p>If the system identifier is a URL, it must be fully resolved
+ * by the application before it is passed to the parser.</p>
+ *
+ * @param systemId The system identifier (URI).
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ * @see #parse(org.xml.sax.InputSource)
+ */
+ public void parse (String systemId)
+ throws IOException, SAXException;
+
+}
diff --git a/src/org/xml/sax/ext/Attributes2.java b/src/org/xml/sax/ext/Attributes2.java
new file mode 100644
index 0000000..cb1d679
--- /dev/null
+++ b/src/org/xml/sax/ext/Attributes2.java
@@ -0,0 +1,132 @@
+// Attributes2.java - extended Attributes
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Attributes2.java,v 1.6 2004/03/08 13:01:00 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * SAX2 extension to augment the per-attribute information
+ * provided though {@link Attributes}.
+ * If an implementation supports this extension, the attributes
+ * provided in {@link org.xml.sax.ContentHandler#startElement
+ * ContentHandler.startElement() } will implement this interface,
+ * and the <em>http://xml.org/sax/features/use-attributes2</em>
+ * feature flag will have the value <em>true</em>.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> XMLReader implementations are not required to support this
+ * information, and it is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>Note that if an attribute was defaulted (<em>!isSpecified()</em>)
+ * it will of necessity also have been declared (<em>isDeclared()</em>)
+ * in the DTD.
+ * Similarly if an attribute's type is anything except CDATA, then it
+ * must have been declared.
+ * </p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public interface Attributes2 extends Attributes
+{
+ /**
+ * Returns false unless the attribute was declared in the DTD.
+ * This helps distinguish two kinds of attributes that SAX reports
+ * as CDATA: ones that were declared (and hence are usually valid),
+ * and those that were not (and which are never valid).
+ *
+ * @param index The attribute index (zero-based).
+ * @return true if the attribute was declared in the DTD,
+ * false otherwise.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not identify an attribute.
+ */
+ public boolean isDeclared (int index);
+
+ /**
+ * Returns false unless the attribute was declared in the DTD.
+ * This helps distinguish two kinds of attributes that SAX reports
+ * as CDATA: ones that were declared (and hence are usually valid),
+ * and those that were not (and which are never valid).
+ *
+ * @param qName The XML qualified (prefixed) name.
+ * @return true if the attribute was declared in the DTD,
+ * false otherwise.
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied name does not identify an attribute.
+ */
+ public boolean isDeclared (String qName);
+
+ /**
+ * Returns false unless the attribute was declared in the DTD.
+ * This helps distinguish two kinds of attributes that SAX reports
+ * as CDATA: ones that were declared (and hence are usually valid),
+ * and those that were not (and which are never valid).
+ *
+ * <p>Remember that since DTDs do not "understand" namespaces, the
+ * namespace URI associated with an attribute may not have come from
+ * the DTD. The declaration will have applied to the attribute's
+ * <em>qName</em>.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return true if the attribute was declared in the DTD,
+ * false otherwise.
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied names do not identify an attribute.
+ */
+ public boolean isDeclared (String uri, String localName);
+
+ /**
+ * Returns true unless the attribute value was provided
+ * by DTD defaulting.
+ *
+ * @param index The attribute index (zero-based).
+ * @return true if the value was found in the XML text,
+ * false if the value was provided by DTD defaulting.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not identify an attribute.
+ */
+ public boolean isSpecified (int index);
+
+ /**
+ * Returns true unless the attribute value was provided
+ * by DTD defaulting.
+ *
+ * <p>Remember that since DTDs do not "understand" namespaces, the
+ * namespace URI associated with an attribute may not have come from
+ * the DTD. The declaration will have applied to the attribute's
+ * <em>qName</em>.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return true if the value was found in the XML text,
+ * false if the value was provided by DTD defaulting.
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied names do not identify an attribute.
+ */
+ public boolean isSpecified (String uri, String localName);
+
+ /**
+ * Returns true unless the attribute value was provided
+ * by DTD defaulting.
+ *
+ * @param qName The XML qualified (prefixed) name.
+ * @return true if the value was found in the XML text,
+ * false if the value was provided by DTD defaulting.
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied name does not identify an attribute.
+ */
+ public boolean isSpecified (String qName);
+}
diff --git a/src/org/xml/sax/ext/Attributes2Impl.java b/src/org/xml/sax/ext/Attributes2Impl.java
new file mode 100644
index 0000000..60b2a40
--- /dev/null
+++ b/src/org/xml/sax/ext/Attributes2Impl.java
@@ -0,0 +1,301 @@
+// Attributes2Impl.java - extended AttributesImpl
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Attributes2Impl.java,v 1.5 2004/03/08 13:01:01 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+
+/**
+ * SAX2 extension helper for additional Attributes information,
+ * implementing the {@link Attributes2} interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p>This is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>The <em>specified</em> flag for each attribute will always
+ * be true, unless it has been set to false in the copy constructor
+ * or using {@link #setSpecified}.
+ * Similarly, the <em>declared</em> flag for each attribute will
+ * always be false, except for defaulted attributes (<em>specified</em>
+ * is false), non-CDATA attributes, or when it is set to true using
+ * {@link #setDeclared}.
+ * If you change an attribute's type by hand, you may need to modify
+ * its <em>declared</em> flag to match.
+ * </p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public class Attributes2Impl extends AttributesImpl implements Attributes2
+{
+ private boolean declared [];
+ private boolean specified [];
+
+
+ /**
+ * Construct a new, empty Attributes2Impl object.
+ */
+ public Attributes2Impl () { }
+
+
+ /**
+ * Copy an existing Attributes or Attributes2 object.
+ * If the object implements Attributes2, values of the
+ * <em>specified</em> and <em>declared</em> flags for each
+ * attribute are copied.
+ * Otherwise the flag values are defaulted to assume no DTD was used,
+ * unless there is evidence to the contrary (such as attributes with
+ * type other than CDATA, which must have been <em>declared</em>).
+ *
+ * <p>This constructor is especially useful inside a
+ * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
+ *
+ * @param atts The existing Attributes object.
+ */
+ public Attributes2Impl (Attributes atts)
+ {
+ super (atts);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of Attributes2
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Returns the current value of the attribute's "declared" flag.
+ */
+ // javadoc mostly from interface
+ public boolean isDeclared (int index)
+ {
+ if (index < 0 || index >= getLength ())
+ throw new ArrayIndexOutOfBoundsException (
+ "No attribute at index: " + index);
+ return declared [index];
+ }
+
+
+ /**
+ * Returns the current value of the attribute's "declared" flag.
+ */
+ // javadoc mostly from interface
+ public boolean isDeclared (String uri, String localName)
+ {
+ int index = getIndex (uri, localName);
+
+ if (index < 0)
+ throw new IllegalArgumentException (
+ "No such attribute: local=" + localName
+ + ", namespace=" + uri);
+ return declared [index];
+ }
+
+
+ /**
+ * Returns the current value of the attribute's "declared" flag.
+ */
+ // javadoc mostly from interface
+ public boolean isDeclared (String qName)
+ {
+ int index = getIndex (qName);
+
+ if (index < 0)
+ throw new IllegalArgumentException (
+ "No such attribute: " + qName);
+ return declared [index];
+ }
+
+
+ /**
+ * Returns the current value of an attribute's "specified" flag.
+ *
+ * @param index The attribute index (zero-based).
+ * @return current flag value
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not identify an attribute.
+ */
+ public boolean isSpecified (int index)
+ {
+ if (index < 0 || index >= getLength ())
+ throw new ArrayIndexOutOfBoundsException (
+ "No attribute at index: " + index);
+ return specified [index];
+ }
+
+
+ /**
+ * Returns the current value of an attribute's "specified" flag.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return current flag value
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied names do not identify an attribute.
+ */
+ public boolean isSpecified (String uri, String localName)
+ {
+ int index = getIndex (uri, localName);
+
+ if (index < 0)
+ throw new IllegalArgumentException (
+ "No such attribute: local=" + localName
+ + ", namespace=" + uri);
+ return specified [index];
+ }
+
+
+ /**
+ * Returns the current value of an attribute's "specified" flag.
+ *
+ * @param qName The XML qualified (prefixed) name.
+ * @return current flag value
+ * @exception java.lang.IllegalArgumentException When the
+ * supplied name does not identify an attribute.
+ */
+ public boolean isSpecified (String qName)
+ {
+ int index = getIndex (qName);
+
+ if (index < 0)
+ throw new IllegalArgumentException (
+ "No such attribute: " + qName);
+ return specified [index];
+ }
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Manipulators
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Copy an entire Attributes object. The "specified" flags are
+ * assigned as true, and "declared" flags as false (except when
+ * an attribute's type is not CDATA),
+ * unless the object is an Attributes2 object.
+ * In that case those flag values are all copied.
+ *
+ * @see AttributesImpl#setAttributes
+ */
+ public void setAttributes (Attributes atts)
+ {
+ int length = atts.getLength ();
+
+ super.setAttributes (atts);
+ declared = new boolean [length];
+ specified = new boolean [length];
+
+ if (atts instanceof Attributes2) {
+ Attributes2 a2 = (Attributes2) atts;
+ for (int i = 0; i < length; i++) {
+ declared [i] = a2.isDeclared (i);
+ specified [i] = a2.isSpecified (i);
+ }
+ } else {
+ for (int i = 0; i < length; i++) {
+ declared [i] = !"CDATA".equals (atts.getType (i));
+ specified [i] = true;
+ }
+ }
+ }
+
+
+ /**
+ * Add an attribute to the end of the list, setting its
+ * "specified" flag to true. To set that flag's value
+ * to false, use {@link #setSpecified}.
+ *
+ * <p>Unless the attribute <em>type</em> is CDATA, this attribute
+ * is marked as being declared in the DTD. To set that flag's value
+ * to true for CDATA attributes, use {@link #setDeclared}.
+ *
+ * @see AttributesImpl#addAttribute
+ */
+ public void addAttribute (String uri, String localName, String qName,
+ String type, String value)
+ {
+ super.addAttribute (uri, localName, qName, type, value);
+
+ int length = getLength ();
+
+ if (length < specified.length) {
+ boolean newFlags [];
+
+ newFlags = new boolean [length];
+ System.arraycopy (declared, 0, newFlags, 0, declared.length);
+ declared = newFlags;
+
+ newFlags = new boolean [length];
+ System.arraycopy (specified, 0, newFlags, 0, specified.length);
+ specified = newFlags;
+ }
+
+ specified [length - 1] = true;
+ declared [length - 1] = !"CDATA".equals (type);
+ }
+
+
+ // javadoc entirely from superclass
+ public void removeAttribute (int index)
+ {
+ int origMax = getLength () - 1;
+
+ super.removeAttribute (index);
+ if (index != origMax) {
+ System.arraycopy (declared, index + 1, declared, index,
+ origMax - index);
+ System.arraycopy (specified, index + 1, specified, index,
+ origMax - index);
+ }
+ }
+
+
+ /**
+ * Assign a value to the "declared" flag of a specific attribute.
+ * This is normally needed only for attributes of type CDATA,
+ * including attributes whose type is changed to or from CDATA.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param value The desired flag value.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not identify an attribute.
+ * @see #setType
+ */
+ public void setDeclared (int index, boolean value)
+ {
+ if (index < 0 || index >= getLength ())
+ throw new ArrayIndexOutOfBoundsException (
+ "No attribute at index: " + index);
+ declared [index] = value;
+ }
+
+
+ /**
+ * Assign a value to the "specified" flag of a specific attribute.
+ * This is the only way this flag can be cleared, except clearing
+ * by initialization with the copy constructor.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param value The desired flag value.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not identify an attribute.
+ */
+ public void setSpecified (int index, boolean value)
+ {
+ if (index < 0 || index >= getLength ())
+ throw new ArrayIndexOutOfBoundsException (
+ "No attribute at index: " + index);
+ specified [index] = value;
+ }
+}
diff --git a/src/org/xml/sax/ext/DeclHandler.java b/src/org/xml/sax/ext/DeclHandler.java
new file mode 100644
index 0000000..865e33c
--- /dev/null
+++ b/src/org/xml/sax/ext/DeclHandler.java
@@ -0,0 +1,146 @@
+// DeclHandler.java - Optional handler for DTD declaration events.
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: DeclHandler.java,v 1.6 2004/04/22 13:28:49 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.SAXException;
+
+
+/**
+ * SAX2 extension handler for DTD declaration events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is an optional extension handler for SAX2 to provide more
+ * complete information about DTD declarations in an XML document.
+ * XML readers are not required to recognize this handler, and it
+ * is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>Note that data-related DTD declarations (unparsed entities and
+ * notations) are already reported through the {@link
+ * org.xml.sax.DTDHandler DTDHandler} interface.</p>
+ *
+ * <p>If you are using the declaration handler together with a lexical
+ * handler, all of the events will occur between the
+ * {@link org.xml.sax.ext.LexicalHandler#startDTD startDTD} and the
+ * {@link org.xml.sax.ext.LexicalHandler#endDTD endDTD} events.</p>
+ *
+ * <p>To set the DeclHandler for an XML reader, use the
+ * {@link org.xml.sax.XMLReader#setProperty setProperty} method
+ * with the property name
+ * <code>http://xml.org/sax/properties/declaration-handler</code>
+ * and an object implementing this interface (or null) as the value.
+ * If the reader does not report declaration events, it will throw a
+ * {@link org.xml.sax.SAXNotRecognizedException SAXNotRecognizedException}
+ * when you attempt to register the handler.</p>
+ *
+ * @since SAX 2.0 (extensions 1.0)
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public interface DeclHandler
+{
+
+ /**
+ * Report an element type declaration.
+ *
+ * <p>The content model will consist of the string "EMPTY", the
+ * string "ANY", or a parenthesised group, optionally followed
+ * by an occurrence indicator. The model will be normalized so
+ * that all parameter entities are fully resolved and all whitespace
+ * is removed,and will include the enclosing parentheses. Other
+ * normalization (such as removing redundant parentheses or
+ * simplifying occurrence indicators) is at the discretion of the
+ * parser.</p>
+ *
+ * @param name The element type name.
+ * @param model The content model as a normalized string.
+ * @exception SAXException The application may raise an exception.
+ */
+ public abstract void elementDecl (String name, String model)
+ throws SAXException;
+
+
+ /**
+ * Report an attribute type declaration.
+ *
+ * <p>Only the effective (first) declaration for an attribute will
+ * be reported. The type will be one of the strings "CDATA",
+ * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
+ * "ENTITIES", a parenthesized token group with
+ * the separator "|" and all whitespace removed, or the word
+ * "NOTATION" followed by a space followed by a parenthesized
+ * token group with all whitespace removed.</p>
+ *
+ * <p>The value will be the value as reported to applications,
+ * appropriately normalized and with entity and character
+ * references expanded. </p>
+ *
+ * @param eName The name of the associated element.
+ * @param aName The name of the attribute.
+ * @param type A string representing the attribute type.
+ * @param mode A string representing the attribute defaulting mode
+ * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
+ * none of these applies.
+ * @param value A string representing the attribute's default value,
+ * or null if there is none.
+ * @exception SAXException The application may raise an exception.
+ */
+ public abstract void attributeDecl (String eName,
+ String aName,
+ String type,
+ String mode,
+ String value)
+ throws SAXException;
+
+
+ /**
+ * Report an internal entity declaration.
+ *
+ * <p>Only the effective (first) declaration for each entity
+ * will be reported. All parameter entities in the value
+ * will be expanded, but general entities will not.</p>
+ *
+ * @param name The name of the entity. If it is a parameter
+ * entity, the name will begin with '%'.
+ * @param value The replacement text of the entity.
+ * @exception SAXException The application may raise an exception.
+ * @see #externalEntityDecl
+ * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+ */
+ public abstract void internalEntityDecl (String name, String value)
+ throws SAXException;
+
+
+ /**
+ * Report a parsed external entity declaration.
+ *
+ * <p>Only the effective (first) declaration for each entity
+ * will be reported.</p>
+ *
+ * <p>If the system identifier is a URL, the parser must resolve it
+ * fully before passing it to the application.</p>
+ *
+ * @param name The name of the entity. If it is a parameter
+ * entity, the name will begin with '%'.
+ * @param publicId The entity's public identifier, or null if none
+ * was given.
+ * @param systemId The entity's system identifier.
+ * @exception SAXException The application may raise an exception.
+ * @see #internalEntityDecl
+ * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+ */
+ public abstract void externalEntityDecl (String name, String publicId,
+ String systemId)
+ throws SAXException;
+
+}
+
+// end of DeclHandler.java
diff --git a/src/org/xml/sax/ext/DefaultHandler2.java b/src/org/xml/sax/ext/DefaultHandler2.java
new file mode 100644
index 0000000..1458dd0
--- /dev/null
+++ b/src/org/xml/sax/ext/DefaultHandler2.java
@@ -0,0 +1,130 @@
+// DefaultHandler2.java - extended DefaultHandler
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: DefaultHandler2.java,v 1.3 2002/01/12 19:04:19 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import java.io.IOException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * This class extends the SAX2 base handler class to support the
+ * SAX2 {@link LexicalHandler}, {@link DeclHandler}, and
+ * {@link EntityResolver2} extensions. Except for overriding the
+ * original SAX1 {@link DefaultHandler#resolveEntity resolveEntity()}
+ * method the added handler methods just return. Subclassers may
+ * override everything on a method-by-method basis.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> <em>Note:</em> this class might yet learn that the
+ * <em>ContentHandler.setDocumentLocator()</em> call might be passed a
+ * {@link Locator2} object, and that the
+ * <em>ContentHandler.startElement()</em> call might be passed a
+ * {@link Attributes2} object.
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public class DefaultHandler2 extends DefaultHandler
+ implements LexicalHandler, DeclHandler, EntityResolver2
+{
+ /** Constructs a handler which ignores all parsing events. */
+ public DefaultHandler2 () { }
+
+
+ // SAX2 ext-1.0 LexicalHandler
+
+ public void startCDATA ()
+ throws SAXException
+ {}
+
+ public void endCDATA ()
+ throws SAXException
+ {}
+
+ public void startDTD (String name, String publicId, String systemId)
+ throws SAXException
+ {}
+
+ public void endDTD ()
+ throws SAXException
+ {}
+
+ public void startEntity (String name)
+ throws SAXException
+ {}
+
+ public void endEntity (String name)
+ throws SAXException
+ {}
+
+ public void comment (char ch [], int start, int length)
+ throws SAXException
+ { }
+
+
+ // SAX2 ext-1.0 DeclHandler
+
+ public void attributeDecl (String eName, String aName,
+ String type, String mode, String value)
+ throws SAXException
+ {}
+
+ public void elementDecl (String name, String model)
+ throws SAXException
+ {}
+
+ public void externalEntityDecl (String name,
+ String publicId, String systemId)
+ throws SAXException
+ {}
+
+ public void internalEntityDecl (String name, String value)
+ throws SAXException
+ {}
+
+ // SAX2 ext-1.1 EntityResolver2
+
+ /**
+ * Tells the parser that if no external subset has been declared
+ * in the document text, none should be used.
+ */
+ public InputSource getExternalSubset (String name, String baseURI)
+ throws SAXException, IOException
+ { return null; }
+
+ /**
+ * Tells the parser to resolve the systemId against the baseURI
+ * and read the entity text from that resulting absolute URI.
+ * Note that because the older
+ * {@link DefaultHandler#resolveEntity DefaultHandler.resolveEntity()},
+ * method is overridden to call this one, this method may sometimes
+ * be invoked with null <em>name</em> and <em>baseURI</em>, and
+ * with the <em>systemId</em> already absolutized.
+ */
+ public InputSource resolveEntity (String name, String publicId,
+ String baseURI, String systemId)
+ throws SAXException, IOException
+ { return null; }
+
+ // SAX1 EntityResolver
+
+ /**
+ * Invokes
+ * {@link EntityResolver2#resolveEntity EntityResolver2.resolveEntity()}
+ * with null entity name and base URI.
+ * You only need to override that method to use this class.
+ */
+ public InputSource resolveEntity (String publicId, String systemId)
+ throws SAXException, IOException
+ { return resolveEntity (null, publicId, null, systemId); }
+}
diff --git a/src/org/xml/sax/ext/EntityResolver2.java b/src/org/xml/sax/ext/EntityResolver2.java
new file mode 100644
index 0000000..a1108a3
--- /dev/null
+++ b/src/org/xml/sax/ext/EntityResolver2.java
@@ -0,0 +1,197 @@
+// EntityResolver2.java - Extended SAX entity resolver.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: EntityResolver2.java,v 1.2 2002/01/12 19:20:08 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import java.io.IOException;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Extended interface for mapping external entity references to input
+ * sources, or providing a missing external subset. The
+ * {@link XMLReader#setEntityResolver XMLReader.setEntityResolver()} method
+ * is used to provide implementations of this interface to parsers.
+ * When a parser uses the methods in this interface, the
+ * {@link EntityResolver2#resolveEntity EntityResolver2.resolveEntity()}
+ * method (in this interface) is used <em>instead of</em> the older (SAX 1.0)
+ * {@link EntityResolver#resolveEntity EntityResolver.resolveEntity()} method.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p>If a SAX application requires the customized handling which this
+ * interface defines for external entities, it must ensure that it uses
+ * an XMLReader with the
+ * <em>http://xml.org/sax/features/use-entity-resolver2</em> feature flag
+ * set to <em>true</em> (which is its default value when the feature is
+ * recognized). If that flag is unrecognized, or its value is false,
+ * or the resolver does not implement this interface, then only the
+ * {@link EntityResolver} method will be used.
+ * </p>
+ *
+ * <p>That supports three categories of application that modify entity
+ * resolution. <em>Old Style</em> applications won't know about this interface;
+ * they will provide an EntityResolver.
+ * <em>Transitional Mode</em> provide an EntityResolver2 and automatically
+ * get the benefit of its methods in any systems (parsers or other tools)
+ * supporting it, due to polymorphism.
+ * Both <em>Old Style</em> and <em>Transitional Mode</em> applications will
+ * work with any SAX2 parser.
+ * <em>New style</em> applications will fail to run except on SAX2 parsers
+ * that support this particular feature.
+ * They will insist that feature flag have a value of "true", and the
+ * EntityResolver2 implementation they provide might throw an exception
+ * if the original SAX 1.0 style entity resolution method is invoked.
+ * </p>
+ *
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBD
+ */
+public interface EntityResolver2 extends EntityResolver
+{
+ /**
+ * Allows applications to provide an external subset for documents
+ * that don't explicitly define one. Documents with DOCTYPE declarations
+ * that omit an external subset can thus augment the declarations
+ * available for validation, entity processing, and attribute processing
+ * (normalization, defaulting, and reporting types including ID).
+ * This augmentation is reported
+ * through the {@link LexicalHandler#startDTD startDTD()} method as if
+ * the document text had originally included the external subset;
+ * this callback is made before any internal subset data or errors
+ * are reported.</p>
+ *
+ * <p>This method can also be used with documents that have no DOCTYPE
+ * declaration. When the root element is encountered,
+ * but no DOCTYPE declaration has been seen, this method is
+ * invoked. If it returns a value for the external subset, that root
+ * element is declared to be the root element, giving the effect of
+ * splicing a DOCTYPE declaration at the end the prolog of a document
+ * that could not otherwise be valid. The sequence of parser callbacks
+ * in that case logically resembles this:</p>
+ *
+ * <pre>
+ * ... comments and PIs from the prolog (as usual)
+ * startDTD ("rootName", source.getPublicId (), source.getSystemId ());
+ * startEntity ("[dtd]");
+ * ... declarations, comments, and PIs from the external subset
+ * endEntity ("[dtd]");
+ * endDTD ();
+ * ... then the rest of the document (as usual)
+ * startElement (..., "rootName", ...);
+ * </pre>
+ *
+ * <p>Note that the InputSource gets no further resolution.
+ * Implementations of this method may wish to invoke
+ * {@link #resolveEntity resolveEntity()} to gain benefits such as use
+ * of local caches of DTD entities. Also, this method will never be
+ * used by a (non-validating) processor that is not including external
+ * parameter entities. </p>
+ *
+ * <p>Uses for this method include facilitating data validation when
+ * interoperating with XML processors that would always require
+ * undesirable network accesses for external entities, or which for
+ * other reasons adopt a "no DTDs" policy.
+ * Non-validation motives include forcing documents to include DTDs so
+ * that attributes are handled consistently.
+ * For example, an XPath processor needs to know which attibutes have
+ * type "ID" before it can process a widely used type of reference.</p>
+ *
+ * <p><strong>Warning:</strong> Returning an external subset modifies
+ * the input document. By providing definitions for general entities,
+ * it can make a malformed document appear to be well formed.
+ * </p>
+ *
+ * @param name Identifies the document root element. This name comes
+ * from a DOCTYPE declaration (where available) or from the actual
+ * root element.
+ * @param baseURI The document's base URI, serving as an additional
+ * hint for selecting the external subset. This is always an absolute
+ * URI, unless it is null because the XMLReader was given an InputSource
+ * without one.
+ *
+ * @return An InputSource object describing the new external subset
+ * to be used by the parser, or null to indicate that no external
+ * subset is provided.
+ *
+ * @exception SAXException Any SAX exception, possibly wrapping
+ * another exception.
+ * @exception IOException Probably indicating a failure to create
+ * a new InputStream or Reader, or an illegal URL.
+ */
+ public InputSource getExternalSubset (String name, String baseURI)
+ throws SAXException, IOException;
+
+ /**
+ * Allows applications to map references to external entities into input
+ * sources, or tell the parser it should use conventional URI resolution.
+ * This method is only called for external entities which have been
+ * properly declared.
+ * This method provides more flexibility than the {@link EntityResolver}
+ * interface, supporting implementations of more complex catalogue
+ * schemes such as the one defined by the <a href=
+ "http://www.oasis-open.org/committees/entity/spec-2001-08-06.html"
+ >OASIS XML Catalogs</a> specification.</p>
+ *
+ * <p>Parsers configured to use this resolver method will call it
+ * to determine the input source to use for any external entity
+ * being included because of a reference in the XML text.
+ * That excludes the document entity, and any external entity returned
+ * by {@link #getExternalSubset getExternalSubset()}.
+ * When a (non-validating) processor is configured not to include
+ * a class of entities (parameter or general) through use of feature
+ * flags, this method is not invoked for such entities. </p>
+ *
+ * <p>Note that the entity naming scheme used here is the same one
+ * used in the {@link LexicalHandler}, or in the {@link
+ org.xml.sax.ContentHandler#skippedEntity
+ ContentHandler.skippedEntity()}
+ * method. </p>
+ *
+ * @param name Identifies the external entity being resolved.
+ * Either "[dtd]" for the external subset, or a name starting
+ * with "%" to indicate a parameter entity, or else the name of
+ * a general entity. This is never null when invoked by a SAX2
+ * parser.
+ * @param publicId The public identifier of the external entity being
+ * referenced (normalized as required by the XML specification), or
+ * null if none was supplied.
+ * @param baseURI The URI with respect to which relative systemIDs
+ * are interpreted. This is always an absolute URI, unless it is
+ * null (likely because the XMLReader was given an InputSource without
+ * one). This URI is defined by the XML specification to be the one
+ * associated with the "<" starting the relevant declaration.
+ * @param systemId The system identifier of the external entity
+ * being referenced; either a relative or absolute URI.
+ * This is never null when invoked by a SAX2 parser; only declared
+ * entities, and any external subset, are resolved by such parsers.
+ *
+ * @return An InputSource object describing the new input source to
+ * be used by the parser. Returning null directs the parser to
+ * resolve the system ID against the base URI and open a connection
+ * to resulting URI.
+ *
+ * @exception SAXException Any SAX exception, possibly wrapping
+ * another exception.
+ * @exception IOException Probably indicating a failure to create
+ * a new InputStream or Reader, or an illegal URL.
+ */
+ public InputSource resolveEntity (
+ String name,
+ String publicId,
+ String baseURI,
+ String systemId
+ ) throws SAXException, IOException;
+}
diff --git a/src/org/xml/sax/ext/LexicalHandler.java b/src/org/xml/sax/ext/LexicalHandler.java
new file mode 100644
index 0000000..d63d87f
--- /dev/null
+++ b/src/org/xml/sax/ext/LexicalHandler.java
@@ -0,0 +1,212 @@
+// LexicalHandler.java - optional handler for lexical parse events.
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: LexicalHandler.java,v 1.5 2002/01/30 21:00:44 dbrownell Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.SAXException;
+
+/**
+ * SAX2 extension handler for lexical events.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This is an optional extension handler for SAX2 to provide
+ * lexical information about an XML document, such as comments
+ * and CDATA section boundaries.
+ * XML readers are not required to recognize this handler, and it
+ * is not part of core-only SAX2 distributions.</p>
+ *
+ * <p>The events in the lexical handler apply to the entire document,
+ * not just to the document element, and all lexical handler events
+ * must appear between the content handler's startDocument and
+ * endDocument events.</p>
+ *
+ * <p>To set the LexicalHandler for an XML reader, use the
+ * {@link org.xml.sax.XMLReader#setProperty setProperty} method
+ * with the property name
+ * <code>http://xml.org/sax/properties/lexical-handler</code>
+ * and an object implementing this interface (or null) as the value.
+ * If the reader does not report lexical events, it will throw a
+ * {@link org.xml.sax.SAXNotRecognizedException SAXNotRecognizedException}
+ * when you attempt to register the handler.</p>
+ *
+ * @since SAX 2.0 (extensions 1.0)
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public interface LexicalHandler
+{
+
+ /**
+ * Report the start of DTD declarations, if any.
+ *
+ * <p>This method is intended to report the beginning of the
+ * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+ * this method will not be invoked.</p>
+ *
+ * <p>All declarations reported through
+ * {@link org.xml.sax.DTDHandler DTDHandler} or
+ * {@link org.xml.sax.ext.DeclHandler DeclHandler} events must appear
+ * between the startDTD and {@link #endDTD endDTD} events.
+ * Declarations are assumed to belong to the internal DTD subset
+ * unless they appear between {@link #startEntity startEntity}
+ * and {@link #endEntity endEntity} events. Comments and
+ * processing instructions from the DTD should also be reported
+ * between the startDTD and endDTD events, in their original
+ * order of (logical) occurrence; they are not required to
+ * appear in their correct locations relative to DTDHandler
+ * or DeclHandler events, however.</p>
+ *
+ * <p>Note that the start/endDTD events will appear within
+ * the start/endDocument events from ContentHandler and
+ * before the first
+ * {@link org.xml.sax.ContentHandler#startElement startElement}
+ * event.</p>
+ *
+ * @param name The document type name.
+ * @param publicId The declared public identifier for the
+ * external DTD subset, or null if none was declared.
+ * @param systemId The declared system identifier for the
+ * external DTD subset, or null if none was declared.
+ * (Note that this is not resolved against the document
+ * base URI.)
+ * @exception SAXException The application may raise an
+ * exception.
+ * @see #endDTD
+ * @see #startEntity
+ */
+ public abstract void startDTD (String name, String publicId,
+ String systemId)
+ throws SAXException;
+
+
+ /**
+ * Report the end of DTD declarations.
+ *
+ * <p>This method is intended to report the end of the
+ * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+ * this method will not be invoked.</p>
+ *
+ * @exception SAXException The application may raise an exception.
+ * @see #startDTD
+ */
+ public abstract void endDTD ()
+ throws SAXException;
+
+
+ /**
+ * Report the beginning of some internal and external XML entities.
+ *
+ * <p>The reporting of parameter entities (including
+ * the external DTD subset) is optional, and SAX2 drivers that
+ * report LexicalHandler events may not implement it; you can use the
+ * <code
+ * >http://xml.org/sax/features/lexical-handler/parameter-entities</code>
+ * feature to query or control the reporting of parameter entities.</p>
+ *
+ * <p>General entities are reported with their regular names,
+ * parameter entities have '%' prepended to their names, and
+ * the external DTD subset has the pseudo-entity name "[dtd]".</p>
+ *
+ * <p>When a SAX2 driver is providing these events, all other
+ * events must be properly nested within start/end entity
+ * events. There is no additional requirement that events from
+ * {@link org.xml.sax.ext.DeclHandler DeclHandler} or
+ * {@link org.xml.sax.DTDHandler DTDHandler} be properly ordered.</p>
+ *
+ * <p>Note that skipped entities will be reported through the
+ * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
+ * event, which is part of the ContentHandler interface.</p>
+ *
+ * <p>Because of the streaming event model that SAX uses, some
+ * entity boundaries cannot be reported under any
+ * circumstances:</p>
+ *
+ * <ul>
+ * <li>general entities within attribute values</li>
+ * <li>parameter entities within declarations</li>
+ * </ul>
+ *
+ * <p>These will be silently expanded, with no indication of where
+ * the original entity boundaries were.</p>
+ *
+ * <p>Note also that the boundaries of character references (which
+ * are not really entities anyway) are not reported.</p>
+ *
+ * <p>All start/endEntity events must be properly nested.
+ *
+ * @param name The name of the entity. If it is a parameter
+ * entity, the name will begin with '%', and if it is the
+ * external DTD subset, it will be "[dtd]".
+ * @exception SAXException The application may raise an exception.
+ * @see #endEntity
+ * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
+ * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
+ */
+ public abstract void startEntity (String name)
+ throws SAXException;
+
+
+ /**
+ * Report the end of an entity.
+ *
+ * @param name The name of the entity that is ending.
+ * @exception SAXException The application may raise an exception.
+ * @see #startEntity
+ */
+ public abstract void endEntity (String name)
+ throws SAXException;
+
+
+ /**
+ * Report the start of a CDATA section.
+ *
+ * <p>The contents of the CDATA section will be reported through
+ * the regular {@link org.xml.sax.ContentHandler#characters
+ * characters} event; this event is intended only to report
+ * the boundary.</p>
+ *
+ * @exception SAXException The application may raise an exception.
+ * @see #endCDATA
+ */
+ public abstract void startCDATA ()
+ throws SAXException;
+
+
+ /**
+ * Report the end of a CDATA section.
+ *
+ * @exception SAXException The application may raise an exception.
+ * @see #startCDATA
+ */
+ public abstract void endCDATA ()
+ throws SAXException;
+
+
+ /**
+ * Report an XML comment anywhere in the document.
+ *
+ * <p>This callback will be used for comments inside or outside the
+ * document element, including comments in the external DTD
+ * subset (if read). Comments in the DTD must be properly
+ * nested inside start/endDTD and start/endEntity events (if
+ * used).</p>
+ *
+ * @param ch An array holding the characters in the comment.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use from the array.
+ * @exception SAXException The application may raise an exception.
+ */
+ public abstract void comment (char ch[], int start, int length)
+ throws SAXException;
+
+}
+
+// end of LexicalHandler.java
diff --git a/src/org/xml/sax/ext/Locator2.java b/src/org/xml/sax/ext/Locator2.java
new file mode 100644
index 0000000..6de9a16
--- /dev/null
+++ b/src/org/xml/sax/ext/Locator2.java
@@ -0,0 +1,75 @@
+// Locator2.java - extended Locator
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Locator2.java,v 1.5 2004/03/17 14:30:10 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Locator;
+
+
+/**
+ * SAX2 extension to augment the entity information provided
+ * though a {@link Locator}.
+ * If an implementation supports this extension, the Locator
+ * provided in {@link org.xml.sax.ContentHandler#setDocumentLocator
+ * ContentHandler.setDocumentLocator() } will implement this
+ * interface, and the
+ * <em>http://xml.org/sax/features/use-locator2</em> feature
+ * flag will have the value <em>true</em>.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> XMLReader implementations are not required to support this
+ * information, and it is not part of core-only SAX2 distributions.</p>
+ *
+ * @since SAX 2.0 (extensions 1.1 alpha)
+ * @author David Brownell
+ * @version TBS
+ */
+public interface Locator2 extends Locator
+{
+ /**
+ * Returns the version of XML used for the entity. This will
+ * normally be the identifier from the current entity's
+ * <em><?xml version='...' ...?></em> declaration,
+ * or be defaulted by the parser.
+ *
+ * @return Identifier for the XML version being used to interpret
+ * the entity's text, or null if that information is not yet
+ * available in the current parsing state.
+ */
+ public String getXMLVersion ();
+
+ /**
+ * Returns the name of the character encoding for the entity.
+ * If the encoding was declared externally (for example, in a MIME
+ * Content-Type header), that will be the name returned. Else if there
+ * was an <em><?xml ...encoding='...'?></em> declaration at
+ * the start of the document, that encoding name will be returned.
+ * Otherwise the encoding will been inferred (normally to be UTF-8, or
+ * some UTF-16 variant), and that inferred name will be returned.
+ *
+ * <p>When an {@link org.xml.sax.InputSource InputSource} is used
+ * to provide an entity's character stream, this method returns the
+ * encoding provided in that input stream.
+ *
+ * <p> Note that some recent W3C specifications require that text
+ * in some encodings be normalized, using Unicode Normalization
+ * Form C, before processing. Such normalization must be performed
+ * by applications, and would normally be triggered based on the
+ * value returned by this method.
+ *
+ * <p> Encoding names may be those used by the underlying JVM,
+ * and comparisons should be case-insensitive.
+ *
+ * @return Name of the character encoding being used to interpret
+ * * the entity's text, or null if this was not provided for a *
+ * character stream passed through an InputSource or is otherwise
+ * not yet available in the current parsing state.
+ */
+ public String getEncoding ();
+}
diff --git a/src/org/xml/sax/ext/Locator2Impl.java b/src/org/xml/sax/ext/Locator2Impl.java
new file mode 100644
index 0000000..6577ea9
--- /dev/null
+++ b/src/org/xml/sax/ext/Locator2Impl.java
@@ -0,0 +1,101 @@
+// Locator2Impl.java - extended LocatorImpl
+// http://www.saxproject.org
+// Public Domain: no warranty.
+// $Id: Locator2Impl.java,v 1.3 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.ext;
+
+import org.xml.sax.Locator;
+import org.xml.sax.helpers.LocatorImpl;
+
+
+/**
+ * SAX2 extension helper for holding additional Entity information,
+ * implementing the {@link Locator2} interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * </blockquote>
+ *
+ * <p> This is not part of core-only SAX2 distributions.</p>
+ *
+ * @since SAX 2.0.2
+ * @author David Brownell
+ * @version TBS
+ */
+public class Locator2Impl extends LocatorImpl implements Locator2
+{
+ private String encoding;
+ private String version;
+
+
+ /**
+ * Construct a new, empty Locator2Impl object.
+ * This will not normally be useful, since the main purpose
+ * of this class is to make a snapshot of an existing Locator.
+ */
+ public Locator2Impl () { }
+
+ /**
+ * Copy an existing Locator or Locator2 object.
+ * If the object implements Locator2, values of the
+ * <em>encoding</em> and <em>version</em>strings are copied,
+ * otherwise they set to <em>null</em>.
+ *
+ * @param locator The existing Locator object.
+ */
+ public Locator2Impl (Locator locator)
+ {
+ super (locator);
+ if (locator instanceof Locator2) {
+ Locator2 l2 = (Locator2) locator;
+
+ version = l2.getXMLVersion ();
+ encoding = l2.getEncoding ();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ // Locator2 method implementations
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns the current value of the version property.
+ *
+ * @see #setXMLVersion
+ */
+ public String getXMLVersion ()
+ { return version; }
+
+ /**
+ * Returns the current value of the encoding property.
+ *
+ * @see #setEncoding
+ */
+ public String getEncoding ()
+ { return encoding; }
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Setters
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Assigns the current value of the version property.
+ *
+ * @param version the new "version" value
+ * @see #getXMLVersion
+ */
+ public void setXMLVersion (String version)
+ { this.version = version; }
+
+ /**
+ * Assigns the current value of the encoding property.
+ *
+ * @param encoding the new "encoding" value
+ * @see #getEncoding
+ */
+ public void setEncoding (String encoding)
+ { this.encoding = encoding; }
+}
diff --git a/src/org/xml/sax/ext/package.html b/src/org/xml/sax/ext/package.html
new file mode 100644
index 0000000..e443df4
--- /dev/null
+++ b/src/org/xml/sax/ext/package.html
@@ -0,0 +1,46 @@
+<HTML><HEAD>
+<!-- $Id: package.html,v 1.8 2002/01/30 21:00:44 dbrownell Exp $ -->
+</HEAD><BODY>
+
+<p>
+This package contains interfaces to SAX2 facilities that
+conformant SAX drivers won't necessarily support.
+
+<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+for more information about SAX.</p>
+
+<p> This package is independent of the SAX2 core, though the functionality
+exposed generally needs to be implemented within a parser core.
+That independence has several consequences:</p>
+
+<ul>
+
+<li>SAX2 drivers are <em>not</em> required to recognize these handlers.
+</li>
+
+<li>You cannot assume that the class files will be present in every SAX2
+installation.</li>
+
+<li>This package may be updated independently of SAX2 (i.e. new
+handlers and classes may be added without updating SAX2 itself).</li>
+
+<li>The new handlers are not implemented by the SAX2
+<code>org.xml.sax.helpers.DefaultHandler</code> or
+<code>org.xml.sax.helpers.XMLFilterImpl</code> classes.
+You can subclass these if you need such behavior, or
+use the helper classes found here.</li>
+
+<li>The handlers need to be registered differently than core SAX2
+handlers.</li>
+
+</ul>
+
+<p>This package, SAX2-ext, is a standardized extension to SAX2. It is
+designed both to allow SAX parsers to pass certain types of information
+to applications, and to serve as a simple model for other SAX2 parser
+extension packages. Not all such extension packages should need to
+be recognized directly by parsers, however.
+As an example, most validation systems can be cleanly layered on top
+of parsers supporting the standardized SAX2 interfaces. </p>
+
+</BODY></HTML>
diff --git a/src/org/xml/sax/helpers/AttributeListImpl.java b/src/org/xml/sax/helpers/AttributeListImpl.java
new file mode 100644
index 0000000..02ef720
--- /dev/null
+++ b/src/org/xml/sax/helpers/AttributeListImpl.java
@@ -0,0 +1,312 @@
+// SAX default implementation for AttributeList.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: AttributeListImpl.java,v 1.6 2002/01/30 20:52:22 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.AttributeList;
+
+import java.util.Vector;
+
+
+/**
+ * Default implementation for AttributeList.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>AttributeList implements the deprecated SAX1 {@link
+ * org.xml.sax.AttributeList AttributeList} interface, and has been
+ * replaced by the new SAX2 {@link org.xml.sax.helpers.AttributesImpl
+ * AttributesImpl} interface.</p>
+ *
+ * <p>This class provides a convenience implementation of the SAX
+ * {@link org.xml.sax.AttributeList AttributeList} interface. This
+ * implementation is useful both for SAX parser writers, who can use
+ * it to provide attributes to the application, and for SAX application
+ * writers, who can use it to create a persistent copy of an element's
+ * attribute specifications:</p>
+ *
+ * <pre>
+ * private AttributeList myatts;
+ *
+ * public void startElement (String name, AttributeList atts)
+ * {
+ * // create a persistent copy of the attribute list
+ * // for use outside this method
+ * myatts = new AttributeListImpl(atts);
+ * [...]
+ * }
+ * </pre>
+ *
+ * <p>Please note that SAX parsers are not required to use this
+ * class to provide an implementation of AttributeList; it is
+ * supplied only as an optional convenience. In particular,
+ * parser writers are encouraged to invent more efficient
+ * implementations.</p>
+ *
+ * @deprecated This class implements a deprecated interface,
+ * {@link org.xml.sax.AttributeList AttributeList};
+ * that interface has been replaced by
+ * {@link org.xml.sax.Attributes Attributes},
+ * which is implemented in the
+ * {@link org.xml.sax.helpers.AttributesImpl
+ * AttributesImpl} helper class.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.AttributeList
+ * @see org.xml.sax.DocumentHandler#startElement
+ */
+public class AttributeListImpl implements AttributeList
+{
+
+ /**
+ * Create an empty attribute list.
+ *
+ * <p>This constructor is most useful for parser writers, who
+ * will use it to create a single, reusable attribute list that
+ * can be reset with the clear method between elements.</p>
+ *
+ * @see #addAttribute
+ * @see #clear
+ */
+ public AttributeListImpl ()
+ {
+ }
+
+
+ /**
+ * Construct a persistent copy of an existing attribute list.
+ *
+ * <p>This constructor is most useful for application writers,
+ * who will use it to create a persistent copy of an existing
+ * attribute list.</p>
+ *
+ * @param atts The attribute list to copy
+ * @see org.xml.sax.DocumentHandler#startElement
+ */
+ public AttributeListImpl (AttributeList atts)
+ {
+ setAttributeList(atts);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Methods specific to this class.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set the attribute list, discarding previous contents.
+ *
+ * <p>This method allows an application writer to reuse an
+ * attribute list easily.</p>
+ *
+ * @param atts The attribute list to copy.
+ */
+ public void setAttributeList (AttributeList atts)
+ {
+ int count = atts.getLength();
+
+ clear();
+
+ for (int i = 0; i < count; i++) {
+ addAttribute(atts.getName(i), atts.getType(i), atts.getValue(i));
+ }
+ }
+
+
+ /**
+ * Add an attribute to an attribute list.
+ *
+ * <p>This method is provided for SAX parser writers, to allow them
+ * to build up an attribute list incrementally before delivering
+ * it to the application.</p>
+ *
+ * @param name The attribute name.
+ * @param type The attribute type ("NMTOKEN" for an enumeration).
+ * @param value The attribute value (must not be null).
+ * @see #removeAttribute
+ * @see org.xml.sax.DocumentHandler#startElement
+ */
+ public void addAttribute (String name, String type, String value)
+ {
+ names.addElement(name);
+ types.addElement(type);
+ values.addElement(value);
+ }
+
+
+ /**
+ * Remove an attribute from the list.
+ *
+ * <p>SAX application writers can use this method to filter an
+ * attribute out of an AttributeList. Note that invoking this
+ * method will change the length of the attribute list and
+ * some of the attribute's indices.</p>
+ *
+ * <p>If the requested attribute is not in the list, this is
+ * a no-op.</p>
+ *
+ * @param name The attribute name.
+ * @see #addAttribute
+ */
+ public void removeAttribute (String name)
+ {
+ int i = names.indexOf(name);
+
+ if (i >= 0) {
+ names.removeElementAt(i);
+ types.removeElementAt(i);
+ values.removeElementAt(i);
+ }
+ }
+
+
+ /**
+ * Clear the attribute list.
+ *
+ * <p>SAX parser writers can use this method to reset the attribute
+ * list between DocumentHandler.startElement events. Normally,
+ * it will make sense to reuse the same AttributeListImpl object
+ * rather than allocating a new one each time.</p>
+ *
+ * @see org.xml.sax.DocumentHandler#startElement
+ */
+ public void clear ()
+ {
+ names.removeAllElements();
+ types.removeAllElements();
+ values.removeAllElements();
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.AttributeList
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the number of attributes in the list.
+ *
+ * @return The number of attributes in the list.
+ * @see org.xml.sax.AttributeList#getLength
+ */
+ public int getLength ()
+ {
+ return names.size();
+ }
+
+
+ /**
+ * Get the name of an attribute (by position).
+ *
+ * @param i The position of the attribute in the list.
+ * @return The attribute name as a string, or null if there
+ * is no attribute at that position.
+ * @see org.xml.sax.AttributeList#getName(int)
+ */
+ public String getName (int i)
+ {
+ if (i < 0) {
+ return null;
+ }
+ try {
+ return (String)names.elementAt(i);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Get the type of an attribute (by position).
+ *
+ * @param i The position of the attribute in the list.
+ * @return The attribute type as a string ("NMTOKEN" for an
+ * enumeration, and "CDATA" if no declaration was
+ * read), or null if there is no attribute at
+ * that position.
+ * @see org.xml.sax.AttributeList#getType(int)
+ */
+ public String getType (int i)
+ {
+ if (i < 0) {
+ return null;
+ }
+ try {
+ return (String)types.elementAt(i);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Get the value of an attribute (by position).
+ *
+ * @param i The position of the attribute in the list.
+ * @return The attribute value as a string, or null if
+ * there is no attribute at that position.
+ * @see org.xml.sax.AttributeList#getValue(int)
+ */
+ public String getValue (int i)
+ {
+ if (i < 0) {
+ return null;
+ }
+ try {
+ return (String)values.elementAt(i);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Get the type of an attribute (by name).
+ *
+ * @param name The attribute name.
+ * @return The attribute type as a string ("NMTOKEN" for an
+ * enumeration, and "CDATA" if no declaration was
+ * read).
+ * @see org.xml.sax.AttributeList#getType(java.lang.String)
+ */
+ public String getType (String name)
+ {
+ return getType(names.indexOf(name));
+ }
+
+
+ /**
+ * Get the value of an attribute (by name).
+ *
+ * @param name The attribute name.
+ * @see org.xml.sax.AttributeList#getValue(java.lang.String)
+ */
+ public String getValue (String name)
+ {
+ return getValue(names.indexOf(name));
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ Vector names = new Vector();
+ Vector types = new Vector();
+ Vector values = new Vector();
+
+}
+
+// end of AttributeListImpl.java
diff --git a/src/org/xml/sax/helpers/AttributesImpl.java b/src/org/xml/sax/helpers/AttributesImpl.java
new file mode 100644
index 0000000..8e86caa
--- /dev/null
+++ b/src/org/xml/sax/helpers/AttributesImpl.java
@@ -0,0 +1,618 @@
+// AttributesImpl.java - default implementation of Attributes.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: AttributesImpl.java,v 1.9 2002/01/30 20:52:24 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * Default implementation of the Attributes interface.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class provides a default implementation of the SAX2
+ * {@link org.xml.sax.Attributes Attributes} interface, with the
+ * addition of manipulators so that the list can be modified or
+ * reused.</p>
+ *
+ * <p>There are two typical uses of this class:</p>
+ *
+ * <ol>
+ * <li>to take a persistent snapshot of an Attributes object
+ * in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
+ * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
+ * </ol>
+ *
+ * <p>This class replaces the now-deprecated SAX1 {@link
+ * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
+ * class; in addition to supporting the updated Attributes
+ * interface rather than the deprecated {@link org.xml.sax.AttributeList
+ * AttributeList} interface, it also includes a much more efficient
+ * implementation using a single array rather than a set of Vectors.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class AttributesImpl implements Attributes
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constructors.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Construct a new, empty AttributesImpl object.
+ */
+ public AttributesImpl ()
+ {
+ length = 0;
+ data = null;
+ }
+
+
+ /**
+ * Copy an existing Attributes object.
+ *
+ * <p>This constructor is especially useful inside a
+ * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
+ *
+ * @param atts The existing Attributes object.
+ */
+ public AttributesImpl (Attributes atts)
+ {
+ setAttributes(atts);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.Attributes.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the number of attributes in the list.
+ *
+ * @return The number of attributes in the list.
+ * @see org.xml.sax.Attributes#getLength
+ */
+ public int getLength ()
+ {
+ return length;
+ }
+
+
+ /**
+ * Return an attribute's Namespace URI.
+ *
+ * @param index The attribute's index (zero-based).
+ * @return The Namespace URI, the empty string if none is
+ * available, or null if the index is out of range.
+ * @see org.xml.sax.Attributes#getURI
+ */
+ public String getURI (int index)
+ {
+ if (index >= 0 && index < length) {
+ return data[index*5];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return an attribute's local name.
+ *
+ * @param index The attribute's index (zero-based).
+ * @return The attribute's local name, the empty string if
+ * none is available, or null if the index if out of range.
+ * @see org.xml.sax.Attributes#getLocalName
+ */
+ public String getLocalName (int index)
+ {
+ if (index >= 0 && index < length) {
+ return data[index*5+1];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return an attribute's qualified (prefixed) name.
+ *
+ * @param index The attribute's index (zero-based).
+ * @return The attribute's qualified name, the empty string if
+ * none is available, or null if the index is out of bounds.
+ * @see org.xml.sax.Attributes#getQName
+ */
+ public String getQName (int index)
+ {
+ if (index >= 0 && index < length) {
+ return data[index*5+2];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return an attribute's type by index.
+ *
+ * @param index The attribute's index (zero-based).
+ * @return The attribute's type, "CDATA" if the type is unknown, or null
+ * if the index is out of bounds.
+ * @see org.xml.sax.Attributes#getType(int)
+ */
+ public String getType (int index)
+ {
+ if (index >= 0 && index < length) {
+ return data[index*5+3];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return an attribute's value by index.
+ *
+ * @param index The attribute's index (zero-based).
+ * @return The attribute's value or null if the index is out of bounds.
+ * @see org.xml.sax.Attributes#getValue(int)
+ */
+ public String getValue (int index)
+ {
+ if (index >= 0 && index < length) {
+ return data[index*5+4];
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Look up an attribute's index by Namespace name.
+ *
+ * <p>In many cases, it will be more efficient to look up the name once and
+ * use the index query methods rather than using the name query methods
+ * repeatedly.</p>
+ *
+ * @param uri The attribute's Namespace URI, or the empty
+ * string if none is available.
+ * @param localName The attribute's local name.
+ * @return The attribute's index, or -1 if none matches.
+ * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
+ */
+ public int getIndex (String uri, String localName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i].equals(uri) && data[i+1].equals(localName)) {
+ return i / 5;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Look up an attribute's index by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attribute's index, or -1 if none matches.
+ * @see org.xml.sax.Attributes#getIndex(java.lang.String)
+ */
+ public int getIndex (String qName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i+2].equals(qName)) {
+ return i / 5;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Look up an attribute's type by Namespace-qualified name.
+ *
+ * @param uri The Namespace URI, or the empty string for a name
+ * with no explicit Namespace URI.
+ * @param localName The local name.
+ * @return The attribute's type, or null if there is no
+ * matching attribute.
+ * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
+ */
+ public String getType (String uri, String localName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i].equals(uri) && data[i+1].equals(localName)) {
+ return data[i+3];
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Look up an attribute's type by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attribute's type, or null if there is no
+ * matching attribute.
+ * @see org.xml.sax.Attributes#getType(java.lang.String)
+ */
+ public String getType (String qName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i+2].equals(qName)) {
+ return data[i+3];
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Look up an attribute's value by Namespace-qualified name.
+ *
+ * @param uri The Namespace URI, or the empty string for a name
+ * with no explicit Namespace URI.
+ * @param localName The local name.
+ * @return The attribute's value, or null if there is no
+ * matching attribute.
+ * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
+ */
+ public String getValue (String uri, String localName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i].equals(uri) && data[i+1].equals(localName)) {
+ return data[i+4];
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Look up an attribute's value by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attribute's value, or null if there is no
+ * matching attribute.
+ * @see org.xml.sax.Attributes#getValue(java.lang.String)
+ */
+ public String getValue (String qName)
+ {
+ int max = length * 5;
+ for (int i = 0; i < max; i += 5) {
+ if (data[i+2].equals(qName)) {
+ return data[i+4];
+ }
+ }
+ return null;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Manipulators.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Clear the attribute list for reuse.
+ *
+ * <p>Note that little memory is freed by this call:
+ * the current array is kept so it can be
+ * reused.</p>
+ */
+ public void clear ()
+ {
+ if (data != null) {
+ for (int i = 0; i < (length * 5); i++)
+ data [i] = null;
+ }
+ length = 0;
+ }
+
+
+ /**
+ * Copy an entire Attributes object.
+ *
+ * <p>It may be more efficient to reuse an existing object
+ * rather than constantly allocating new ones.</p>
+ *
+ * @param atts The attributes to copy.
+ */
+ public void setAttributes (Attributes atts)
+ {
+ clear();
+ length = atts.getLength();
+ if (length > 0) {
+ data = new String[length*5];
+ for (int i = 0; i < length; i++) {
+ data[i*5] = atts.getURI(i);
+ data[i*5+1] = atts.getLocalName(i);
+ data[i*5+2] = atts.getQName(i);
+ data[i*5+3] = atts.getType(i);
+ data[i*5+4] = atts.getValue(i);
+ }
+ }
+ }
+
+
+ /**
+ * Add an attribute to the end of the list.
+ *
+ * <p>For the sake of speed, this method does no checking
+ * to see if the attribute is already in the list: that is
+ * the responsibility of the application.</p>
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * none is available or Namespace processing is not
+ * being performed.
+ * @param localName The local name, or the empty string if
+ * Namespace processing is not being performed.
+ * @param qName The qualified (prefixed) name, or the empty string
+ * if qualified names are not available.
+ * @param type The attribute type as a string.
+ * @param value The attribute value.
+ */
+ public void addAttribute (String uri, String localName, String qName,
+ String type, String value)
+ {
+ ensureCapacity(length+1);
+ data[length*5] = uri;
+ data[length*5+1] = localName;
+ data[length*5+2] = qName;
+ data[length*5+3] = type;
+ data[length*5+4] = value;
+ length++;
+ }
+
+
+ /**
+ * Set an attribute in the list.
+ *
+ * <p>For the sake of speed, this method does no checking
+ * for name conflicts or well-formedness: such checks are the
+ * responsibility of the application.</p>
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param uri The Namespace URI, or the empty string if
+ * none is available or Namespace processing is not
+ * being performed.
+ * @param localName The local name, or the empty string if
+ * Namespace processing is not being performed.
+ * @param qName The qualified name, or the empty string
+ * if qualified names are not available.
+ * @param type The attribute type as a string.
+ * @param value The attribute value.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setAttribute (int index, String uri, String localName,
+ String qName, String type, String value)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5] = uri;
+ data[index*5+1] = localName;
+ data[index*5+2] = qName;
+ data[index*5+3] = type;
+ data[index*5+4] = value;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Remove an attribute from the list.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void removeAttribute (int index)
+ {
+ if (index >= 0 && index < length) {
+ if (index < length - 1) {
+ System.arraycopy(data, (index+1)*5, data, index*5,
+ (length-index-1)*5);
+ }
+ index = (length - 1) * 5;
+ data [index++] = null;
+ data [index++] = null;
+ data [index++] = null;
+ data [index++] = null;
+ data [index] = null;
+ length--;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Set the Namespace URI of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param uri The attribute's Namespace URI, or the empty
+ * string for none.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setURI (int index, String uri)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5] = uri;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Set the local name of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param localName The attribute's local name, or the empty
+ * string for none.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setLocalName (int index, String localName)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5+1] = localName;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Set the qualified name of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param qName The attribute's qualified name, or the empty
+ * string for none.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setQName (int index, String qName)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5+2] = qName;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Set the type of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param type The attribute's type.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setType (int index, String type)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5+3] = type;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+ /**
+ * Set the value of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param value The attribute's value.
+ * @exception java.lang.ArrayIndexOutOfBoundsException When the
+ * supplied index does not point to an attribute
+ * in the list.
+ */
+ public void setValue (int index, String value)
+ {
+ if (index >= 0 && index < length) {
+ data[index*5+4] = value;
+ } else {
+ badIndex(index);
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal methods.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Ensure the internal array's capacity.
+ *
+ * @param n The minimum number of attributes that the array must
+ * be able to hold.
+ */
+ private void ensureCapacity (int n) {
+ if (n <= 0) {
+ return;
+ }
+ int max;
+ if (data == null || data.length == 0) {
+ max = 25;
+ }
+ else if (data.length >= n * 5) {
+ return;
+ }
+ else {
+ max = data.length;
+ }
+ while (max < n * 5) {
+ max *= 2;
+ }
+
+ String newData[] = new String[max];
+ if (length > 0) {
+ System.arraycopy(data, 0, newData, 0, length*5);
+ }
+ data = newData;
+ }
+
+
+ /**
+ * Report a bad array index in a manipulator.
+ *
+ * @param index The index to report.
+ * @exception java.lang.ArrayIndexOutOfBoundsException Always.
+ */
+ private void badIndex (int index)
+ throws ArrayIndexOutOfBoundsException
+ {
+ String msg =
+ "Attempt to modify attribute at illegal index: " + index;
+ throw new ArrayIndexOutOfBoundsException(msg);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ int length;
+ String data [];
+
+}
+
+// end of AttributesImpl.java
+
diff --git a/src/org/xml/sax/helpers/DefaultHandler.java b/src/org/xml/sax/helpers/DefaultHandler.java
new file mode 100644
index 0000000..f0649db
--- /dev/null
+++ b/src/org/xml/sax/helpers/DefaultHandler.java
@@ -0,0 +1,467 @@
+// DefaultHandler.java - default implementation of the core handlers.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: DefaultHandler.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * Default base class for SAX2 event handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is available as a convenience base class for SAX2
+ * applications: it provides default implementations for all of the
+ * callbacks in the four core SAX2 handler classes:</p>
+ *
+ * <ul>
+ * <li>{@link org.xml.sax.EntityResolver EntityResolver}</li>
+ * <li>{@link org.xml.sax.DTDHandler DTDHandler}</li>
+ * <li>{@link org.xml.sax.ContentHandler ContentHandler}</li>
+ * <li>{@link org.xml.sax.ErrorHandler ErrorHandler}</li>
+ * </ul>
+ *
+ * <p>Application writers can extend this class when they need to
+ * implement only part of an interface; parser writers can
+ * instantiate this class to provide default handlers when the
+ * application has not supplied its own.</p>
+ *
+ * <p>This class replaces the deprecated SAX1
+ * {@link org.xml.sax.HandlerBase HandlerBase} class.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson,
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ContentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class DefaultHandler
+ implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of the EntityResolver interface.
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Resolve an external entity.
+ *
+ * <p>Always return null, so that the parser will use the system
+ * identifier provided in the XML document. This method implements
+ * the SAX default behaviour: application writers can override it
+ * in a subclass to do special translations such as catalog lookups
+ * or URI redirection.</p>
+ *
+ * @param publicId The public identifer, or null if none is
+ * available.
+ * @param systemId The system identifier provided in the XML
+ * document.
+ * @return The new input source, or null to require the
+ * default behaviour.
+ * @exception java.io.IOException If there is an error setting
+ * up the new input source.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.EntityResolver#resolveEntity
+ */
+ public InputSource resolveEntity (String publicId, String systemId)
+ throws IOException, SAXException
+ {
+ return null;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of DTDHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive notification of a notation declaration.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass if they wish to keep track of the notations
+ * declared in a document.</p>
+ *
+ * @param name The notation name.
+ * @param publicId The notation public identifier, or null if not
+ * available.
+ * @param systemId The notation system identifier.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DTDHandler#notationDecl
+ */
+ public void notationDecl (String name, String publicId, String systemId)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of an unparsed entity declaration.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to keep track of the unparsed entities
+ * declared in a document.</p>
+ *
+ * @param name The entity name.
+ * @param publicId The entity public identifier, or null if not
+ * available.
+ * @param systemId The entity system identifier.
+ * @param notationName The name of the associated notation.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.DTDHandler#unparsedEntityDecl
+ */
+ public void unparsedEntityDecl (String name, String publicId,
+ String systemId, String notationName)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of ContentHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive a Locator object for document events.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass if they wish to store the locator for use
+ * with other document events.</p>
+ *
+ * @param locator A locator for all SAX document events.
+ * @see org.xml.sax.ContentHandler#setDocumentLocator
+ * @see org.xml.sax.Locator
+ */
+ public void setDocumentLocator (Locator locator)
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the beginning of the document.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the beginning
+ * of a document (such as allocating the root node of a tree or
+ * creating an output file).</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#startDocument
+ */
+ public void startDocument ()
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the end of the document.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the end
+ * of a document (such as finalising a tree or closing an output
+ * file).</p>
+ *
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#endDocument
+ */
+ public void endDocument ()
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the start of a Namespace mapping.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the start of
+ * each Namespace prefix scope (such as storing the prefix mapping).</p>
+ *
+ * @param prefix The Namespace prefix being declared.
+ * @param uri The Namespace URI mapped to the prefix.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#startPrefixMapping
+ */
+ public void startPrefixMapping (String prefix, String uri)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the end of a Namespace mapping.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the end of
+ * each prefix mapping.</p>
+ *
+ * @param prefix The Namespace prefix being declared.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#endPrefixMapping
+ */
+ public void endPrefixMapping (String prefix)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the start of an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the start of
+ * each element (such as allocating a new tree node or writing
+ * output to a file).</p>
+ *
+ * @param uri The Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed.
+ * @param localName The local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed.
+ * @param qName The qualified name (with prefix), or the
+ * empty string if qualified names are not available.
+ * @param attributes The attributes attached to the element. If
+ * there are no attributes, it shall be an empty
+ * Attributes object.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#startElement
+ */
+ public void startElement (String uri, String localName,
+ String qName, Attributes attributes)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of the end of an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions at the end of
+ * each element (such as finalising a tree node or writing
+ * output to a file).</p>
+ *
+ * @param uri The Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed.
+ * @param localName The local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed.
+ * @param qName The qualified name (with prefix), or the
+ * empty string if qualified names are not available.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#endElement
+ */
+ public void endElement (String uri, String localName, String qName)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of character data inside an element.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method to take specific actions for each chunk of character data
+ * (such as adding the data to a node or buffer, or printing it to
+ * a file).</p>
+ *
+ * @param ch The characters.
+ * @param start The start position in the character array.
+ * @param length The number of characters to use from the
+ * character array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#characters
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of ignorable whitespace in element content.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method to take specific actions for each chunk of ignorable
+ * whitespace (such as adding data to a node or buffer, or printing
+ * it to a file).</p>
+ *
+ * @param ch The whitespace characters.
+ * @param start The start position in the character array.
+ * @param length The number of characters to use from the
+ * character array.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of a processing instruction.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions for each
+ * processing instruction, such as setting status variables or
+ * invoking other methods.</p>
+ *
+ * @param target The processing instruction target.
+ * @param data The processing instruction data, or null if
+ * none is supplied.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#processingInstruction
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of a skipped entity.
+ *
+ * <p>By default, do nothing. Application writers may override this
+ * method in a subclass to take specific actions for each
+ * processing instruction, such as setting status variables or
+ * invoking other methods.</p>
+ *
+ * @param name The name of the skipped entity.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ContentHandler#processingInstruction
+ */
+ public void skippedEntity (String name)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Default implementation of the ErrorHandler interface.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Receive notification of a parser warning.
+ *
+ * <p>The default implementation does nothing. Application writers
+ * may override this method in a subclass to take specific actions
+ * for each warning, such as inserting the message in a log file or
+ * printing it to the console.</p>
+ *
+ * @param e The warning information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#warning
+ * @see org.xml.sax.SAXParseException
+ */
+ public void warning (SAXParseException e)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Receive notification of a recoverable parser error.
+ *
+ * <p>The default implementation does nothing. Application writers
+ * may override this method in a subclass to take specific actions
+ * for each error, such as inserting the message in a log file or
+ * printing it to the console.</p>
+ *
+ * @param e The warning information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#warning
+ * @see org.xml.sax.SAXParseException
+ */
+ public void error (SAXParseException e)
+ throws SAXException
+ {
+ // no op
+ }
+
+
+ /**
+ * Report a fatal XML parsing error.
+ *
+ * <p>The default implementation throws a SAXParseException.
+ * Application writers may override this method in a subclass if
+ * they need to take specific actions for each fatal error (such as
+ * collecting all of the errors into a single report): in any case,
+ * the application must stop all regular processing when this
+ * method is invoked, since the document is no longer reliable, and
+ * the parser may no longer report parsing events.</p>
+ *
+ * @param e The error information encoded as an exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.ErrorHandler#fatalError
+ * @see org.xml.sax.SAXParseException
+ */
+ public void fatalError (SAXParseException e)
+ throws SAXException
+ {
+ throw e;
+ }
+
+}
+
+// end of DefaultHandler.java
diff --git a/src/org/xml/sax/helpers/LocatorImpl.java b/src/org/xml/sax/helpers/LocatorImpl.java
new file mode 100644
index 0000000..136e5ef
--- /dev/null
+++ b/src/org/xml/sax/helpers/LocatorImpl.java
@@ -0,0 +1,214 @@
+// SAX default implementation for Locator.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: LocatorImpl.java,v 1.6 2002/01/30 20:52:27 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import org.xml.sax.Locator;
+
+
+/**
+ * Provide an optional convenience implementation of Locator.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is available mainly for application writers, who
+ * can use it to make a persistent snapshot of a locator at any
+ * point during a document parse:</p>
+ *
+ * <pre>
+ * Locator locator;
+ * Locator startloc;
+ *
+ * public void setLocator (Locator locator)
+ * {
+ * // note the locator
+ * this.locator = locator;
+ * }
+ *
+ * public void startDocument ()
+ * {
+ * // save the location of the start of the document
+ * // for future use.
+ * Locator startloc = new LocatorImpl(locator);
+ * }
+ *</pre>
+ *
+ * <p>Normally, parser writers will not use this class, since it
+ * is more efficient to provide location information only when
+ * requested, rather than constantly updating a Locator object.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Locator Locator
+ */
+public class LocatorImpl implements Locator
+{
+
+
+ /**
+ * Zero-argument constructor.
+ *
+ * <p>This will not normally be useful, since the main purpose
+ * of this class is to make a snapshot of an existing Locator.</p>
+ */
+ public LocatorImpl ()
+ {
+ }
+
+
+ /**
+ * Copy constructor.
+ *
+ * <p>Create a persistent copy of the current state of a locator.
+ * When the original locator changes, this copy will still keep
+ * the original values (and it can be used outside the scope of
+ * DocumentHandler methods).</p>
+ *
+ * @param locator The locator to copy.
+ */
+ public LocatorImpl (Locator locator)
+ {
+ setPublicId(locator.getPublicId());
+ setSystemId(locator.getSystemId());
+ setLineNumber(locator.getLineNumber());
+ setColumnNumber(locator.getColumnNumber());
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.Locator
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the saved public identifier.
+ *
+ * @return The public identifier as a string, or null if none
+ * is available.
+ * @see org.xml.sax.Locator#getPublicId
+ * @see #setPublicId
+ */
+ public String getPublicId ()
+ {
+ return publicId;
+ }
+
+
+ /**
+ * Return the saved system identifier.
+ *
+ * @return The system identifier as a string, or null if none
+ * is available.
+ * @see org.xml.sax.Locator#getSystemId
+ * @see #setSystemId
+ */
+ public String getSystemId ()
+ {
+ return systemId;
+ }
+
+
+ /**
+ * Return the saved line number (1-based).
+ *
+ * @return The line number as an integer, or -1 if none is available.
+ * @see org.xml.sax.Locator#getLineNumber
+ * @see #setLineNumber
+ */
+ public int getLineNumber ()
+ {
+ return lineNumber;
+ }
+
+
+ /**
+ * Return the saved column number (1-based).
+ *
+ * @return The column number as an integer, or -1 if none is available.
+ * @see org.xml.sax.Locator#getColumnNumber
+ * @see #setColumnNumber
+ */
+ public int getColumnNumber ()
+ {
+ return columnNumber;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Setters for the properties (not in org.xml.sax.Locator)
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set the public identifier for this locator.
+ *
+ * @param publicId The new public identifier, or null
+ * if none is available.
+ * @see #getPublicId
+ */
+ public void setPublicId (String publicId)
+ {
+ this.publicId = publicId;
+ }
+
+
+ /**
+ * Set the system identifier for this locator.
+ *
+ * @param systemId The new system identifier, or null
+ * if none is available.
+ * @see #getSystemId
+ */
+ public void setSystemId (String systemId)
+ {
+ this.systemId = systemId;
+ }
+
+
+ /**
+ * Set the line number for this locator (1-based).
+ *
+ * @param lineNumber The line number, or -1 if none is available.
+ * @see #getLineNumber
+ */
+ public void setLineNumber (int lineNumber)
+ {
+ this.lineNumber = lineNumber;
+ }
+
+
+ /**
+ * Set the column number for this locator (1-based).
+ *
+ * @param columnNumber The column number, or -1 if none is available.
+ * @see #getColumnNumber
+ */
+ public void setColumnNumber (int columnNumber)
+ {
+ this.columnNumber = columnNumber;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ private String publicId;
+ private String systemId;
+ private int lineNumber;
+ private int columnNumber;
+
+}
+
+// end of LocatorImpl.java
diff --git a/src/org/xml/sax/helpers/NamespaceSupport.java b/src/org/xml/sax/helpers/NamespaceSupport.java
new file mode 100644
index 0000000..3c15f5e
--- /dev/null
+++ b/src/org/xml/sax/helpers/NamespaceSupport.java
@@ -0,0 +1,835 @@
+// NamespaceSupport.java - generic Namespace support for SAX.
+// http://www.saxproject.org
+// Written by David Megginson
+// This class is in the Public Domain. NO WARRANTY!
+// $Id: NamespaceSupport.java,v 1.15 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+
+/**
+ * Encapsulate Namespace logic for use by applications using SAX,
+ * or internally by SAX drivers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class encapsulates the logic of Namespace processing: it
+ * tracks the declarations currently in force for each context and
+ * automatically processes qualified XML names into their Namespace
+ * parts; it can also be used in reverse for generating XML qnames
+ * from Namespaces.</p>
+ *
+ * <p>Namespace support objects are reusable, but the reset method
+ * must be invoked between each session.</p>
+ *
+ * <p>Here is a simple session:</p>
+ *
+ * <pre>
+ * String parts[] = new String[3];
+ * NamespaceSupport support = new NamespaceSupport();
+ *
+ * support.pushContext();
+ * support.declarePrefix("", "http://www.w3.org/1999/xhtml");
+ * support.declarePrefix("dc", "http://www.purl.org/dc#");
+ *
+ * parts = support.processName("p", parts, false);
+ * System.out.println("Namespace URI: " + parts[0]);
+ * System.out.println("Local name: " + parts[1]);
+ * System.out.println("Raw name: " + parts[2]);
+ *
+ * parts = support.processName("dc:title", parts, false);
+ * System.out.println("Namespace URI: " + parts[0]);
+ * System.out.println("Local name: " + parts[1]);
+ * System.out.println("Raw name: " + parts[2]);
+ *
+ * support.popContext();
+ * </pre>
+ *
+ * <p>Note that this class is optimized for the use case where most
+ * elements do not contain Namespace declarations: if the same
+ * prefix/URI mapping is repeated for each context (for example), this
+ * class will be somewhat less efficient.</p>
+ *
+ * <p>Although SAX drivers (parsers) may choose to use this class to
+ * implement namespace handling, they are not required to do so.
+ * Applications must track namespace information themselves if they
+ * want to use namespace information.
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class NamespaceSupport
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constants.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * The XML Namespace URI as a constant.
+ * The value is <code>http://www.w3.org/XML/1998/namespace</code>
+ * as defined in the "Namespaces in XML" * recommendation.
+ *
+ * <p>This is the Namespace URI that is automatically mapped
+ * to the "xml" prefix.</p>
+ */
+ public final static String XMLNS =
+ "http://www.w3.org/XML/1998/namespace";
+
+
+ /**
+ * The namespace declaration URI as a constant.
+ * The value is <code>http://www.w3.org/xmlns/2000/</code>, as defined
+ * in a backwards-incompatible erratum to the "Namespaces in XML"
+ * recommendation. Because that erratum postdated SAX2, SAX2 defaults
+ * to the original recommendation, and does not normally use this URI.
+ *
+ *
+ * <p>This is the Namespace URI that is optionally applied to
+ * <em>xmlns</em> and <em>xmlns:*</em> attributes, which are used to
+ * declare namespaces. </p>
+ *
+ * @since SAX 2.1alpha
+ * @see #setNamespaceDeclUris
+ * @see #isNamespaceDeclUris
+ */
+ public final static String NSDECL =
+ "http://www.w3.org/xmlns/2000/";
+
+
+ /**
+ * An empty enumeration.
+ */
+ private final static Enumeration EMPTY_ENUMERATION =
+ new Vector().elements();
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constructor.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Create a new Namespace support object.
+ */
+ public NamespaceSupport ()
+ {
+ reset();
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Context management.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Reset this Namespace support object for reuse.
+ *
+ * <p>It is necessary to invoke this method before reusing the
+ * Namespace support object for a new session. If namespace
+ * declaration URIs are to be supported, that flag must also
+ * be set to a non-default value.
+ * </p>
+ *
+ * @see #setNamespaceDeclUris
+ */
+ public void reset ()
+ {
+ contexts = new Context[32];
+ namespaceDeclUris = false;
+ contextPos = 0;
+ contexts[contextPos] = currentContext = new Context();
+ currentContext.declarePrefix("xml", XMLNS);
+ }
+
+
+ /**
+ * Start a new Namespace context.
+ * The new context will automatically inherit
+ * the declarations of its parent context, but it will also keep
+ * track of which declarations were made within this context.
+ *
+ * <p>Event callback code should start a new context once per element.
+ * This means being ready to call this in either of two places.
+ * For elements that don't include namespace declarations, the
+ * <em>ContentHandler.startElement()</em> callback is the right place.
+ * For elements with such a declaration, it'd done in the first
+ * <em>ContentHandler.startPrefixMapping()</em> callback.
+ * A boolean flag can be used to
+ * track whether a context has been started yet. When either of
+ * those methods is called, it checks the flag to see if a new context
+ * needs to be started. If so, it starts the context and sets the
+ * flag. After <em>ContentHandler.startElement()</em>
+ * does that, it always clears the flag.
+ *
+ * <p>Normally, SAX drivers would push a new context at the beginning
+ * of each XML element. Then they perform a first pass over the
+ * attributes to process all namespace declarations, making
+ * <em>ContentHandler.startPrefixMapping()</em> callbacks.
+ * Then a second pass is made, to determine the namespace-qualified
+ * names for all attributes and for the element name.
+ * Finally all the information for the
+ * <em>ContentHandler.startElement()</em> callback is available,
+ * so it can then be made.
+ *
+ * <p>The Namespace support object always starts with a base context
+ * already in force: in this context, only the "xml" prefix is
+ * declared.</p>
+ *
+ * @see org.xml.sax.ContentHandler
+ * @see #popContext
+ */
+ public void pushContext ()
+ {
+ int max = contexts.length;
+
+ contexts [contextPos].declsOK = false;
+ contextPos++;
+
+ // Extend the array if necessary
+ if (contextPos >= max) {
+ Context newContexts[] = new Context[max*2];
+ System.arraycopy(contexts, 0, newContexts, 0, max);
+ max *= 2;
+ contexts = newContexts;
+ }
+
+ // Allocate the context if necessary.
+ currentContext = contexts[contextPos];
+ if (currentContext == null) {
+ contexts[contextPos] = currentContext = new Context();
+ }
+
+ // Set the parent, if any.
+ if (contextPos > 0) {
+ currentContext.setParent(contexts[contextPos - 1]);
+ }
+ }
+
+
+ /**
+ * Revert to the previous Namespace context.
+ *
+ * <p>Normally, you should pop the context at the end of each
+ * XML element. After popping the context, all Namespace prefix
+ * mappings that were previously in force are restored.</p>
+ *
+ * <p>You must not attempt to declare additional Namespace
+ * prefixes after popping a context, unless you push another
+ * context first.</p>
+ *
+ * @see #pushContext
+ */
+ public void popContext ()
+ {
+ contexts[contextPos].clear();
+ contextPos--;
+ if (contextPos < 0) {
+ throw new EmptyStackException();
+ }
+ currentContext = contexts[contextPos];
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Operations within a context.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Declare a Namespace prefix. All prefixes must be declared
+ * before they are referenced. For example, a SAX driver (parser)
+ * would scan an element's attributes
+ * in two passes: first for namespace declarations,
+ * then a second pass using {@link #processName processName()} to
+ * interpret prefixes against (potentially redefined) prefixes.
+ *
+ * <p>This method declares a prefix in the current Namespace
+ * context; the prefix will remain in force until this context
+ * is popped, unless it is shadowed in a descendant context.</p>
+ *
+ * <p>To declare the default element Namespace, use the empty string as
+ * the prefix.</p>
+ *
+ * <p>Note that you must <em>not</em> declare a prefix after
+ * you've pushed and popped another Namespace context, or
+ * treated the declarations phase as complete by processing
+ * a prefixed name.</p>
+ *
+ * <p>Note that there is an asymmetry in this library: {@link
+ * #getPrefix getPrefix} will not return the "" prefix,
+ * even if you have declared a default element namespace.
+ * To check for a default namespace,
+ * you have to look it up explicitly using {@link #getURI getURI}.
+ * This asymmetry exists to make it easier to look up prefixes
+ * for attribute names, where the default prefix is not allowed.</p>
+ *
+ * @param prefix The prefix to declare, or the empty string to
+ * indicate the default element namespace. This may never have
+ * the value "xml" or "xmlns".
+ * @param uri The Namespace URI to associate with the prefix.
+ * @return true if the prefix was legal, false otherwise
+ *
+ * @see #processName
+ * @see #getURI
+ * @see #getPrefix
+ */
+ public boolean declarePrefix (String prefix, String uri)
+ {
+ if (prefix.equals("xml") || prefix.equals("xmlns")) {
+ return false;
+ } else {
+ currentContext.declarePrefix(prefix, uri);
+ return true;
+ }
+ }
+
+
+ /**
+ * Process a raw XML qualified name, after all declarations in the
+ * current context have been handled by {@link #declarePrefix
+ * declarePrefix()}.
+ *
+ * <p>This method processes a raw XML qualified name in the
+ * current context by removing the prefix and looking it up among
+ * the prefixes currently declared. The return value will be the
+ * array supplied by the caller, filled in as follows:</p>
+ *
+ * <dl>
+ * <dt>parts[0]</dt>
+ * <dd>The Namespace URI, or an empty string if none is
+ * in use.</dd>
+ * <dt>parts[1]</dt>
+ * <dd>The local name (without prefix).</dd>
+ * <dt>parts[2]</dt>
+ * <dd>The original raw name.</dd>
+ * </dl>
+ *
+ * <p>All of the strings in the array will be internalized. If
+ * the raw name has a prefix that has not been declared, then
+ * the return value will be null.</p>
+ *
+ * <p>Note that attribute names are processed differently than
+ * element names: an unprefixed element name will receive the
+ * default Namespace (if any), while an unprefixed attribute name
+ * will not.</p>
+ *
+ * @param qName The XML qualified name to be processed.
+ * @param parts An array supplied by the caller, capable of
+ * holding at least three members.
+ * @param isAttribute A flag indicating whether this is an
+ * attribute name (true) or an element name (false).
+ * @return The supplied array holding three internalized strings
+ * representing the Namespace URI (or empty string), the
+ * local name, and the XML qualified name; or null if there
+ * is an undeclared prefix.
+ * @see #declarePrefix
+ * @see java.lang.String#intern */
+ public String [] processName (String qName, String parts[],
+ boolean isAttribute)
+ {
+ String myParts[] = currentContext.processName(qName, isAttribute);
+ if (myParts == null) {
+ return null;
+ } else {
+ parts[0] = myParts[0];
+ parts[1] = myParts[1];
+ parts[2] = myParts[2];
+ return parts;
+ }
+ }
+
+
+ /**
+ * Look up a prefix and get the currently-mapped Namespace URI.
+ *
+ * <p>This method looks up the prefix in the current context.
+ * Use the empty string ("") for the default Namespace.</p>
+ *
+ * @param prefix The prefix to look up.
+ * @return The associated Namespace URI, or null if the prefix
+ * is undeclared in this context.
+ * @see #getPrefix
+ * @see #getPrefixes
+ */
+ public String getURI (String prefix)
+ {
+ return currentContext.getURI(prefix);
+ }
+
+
+ /**
+ * Return an enumeration of all prefixes whose declarations are
+ * active in the current context.
+ * This includes declarations from parent contexts that have
+ * not been overridden.
+ *
+ * <p><strong>Note:</strong> if there is a default prefix, it will not be
+ * returned in this enumeration; check for the default prefix
+ * using the {@link #getURI getURI} with an argument of "".</p>
+ *
+ * @return An enumeration of prefixes (never empty).
+ * @see #getDeclaredPrefixes
+ * @see #getURI
+ */
+ public Enumeration getPrefixes ()
+ {
+ return currentContext.getPrefixes();
+ }
+
+
+ /**
+ * Return one of the prefixes mapped to a Namespace URI.
+ *
+ * <p>If more than one prefix is currently mapped to the same
+ * URI, this method will make an arbitrary selection; if you
+ * want all of the prefixes, use the {@link #getPrefixes}
+ * method instead.</p>
+ *
+ * <p><strong>Note:</strong> this will never return the empty (default) prefix;
+ * to check for a default prefix, use the {@link #getURI getURI}
+ * method with an argument of "".</p>
+ *
+ * @param uri the namespace URI
+ * @return one of the prefixes currently mapped to the URI supplied,
+ * or null if none is mapped or if the URI is assigned to
+ * the default namespace
+ * @see #getPrefixes(java.lang.String)
+ * @see #getURI
+ */
+ public String getPrefix (String uri)
+ {
+ return currentContext.getPrefix(uri);
+ }
+
+
+ /**
+ * Return an enumeration of all prefixes for a given URI whose
+ * declarations are active in the current context.
+ * This includes declarations from parent contexts that have
+ * not been overridden.
+ *
+ * <p>This method returns prefixes mapped to a specific Namespace
+ * URI. The xml: prefix will be included. If you want only one
+ * prefix that's mapped to the Namespace URI, and you don't care
+ * which one you get, use the {@link #getPrefix getPrefix}
+ * method instead.</p>
+ *
+ * <p><strong>Note:</strong> the empty (default) prefix is <em>never</em> included
+ * in this enumeration; to check for the presence of a default
+ * Namespace, use the {@link #getURI getURI} method with an
+ * argument of "".</p>
+ *
+ * @param uri The Namespace URI.
+ * @return An enumeration of prefixes (never empty).
+ * @see #getPrefix
+ * @see #getDeclaredPrefixes
+ * @see #getURI
+ */
+ public Enumeration getPrefixes (String uri)
+ {
+ Vector prefixes = new Vector();
+ Enumeration allPrefixes = getPrefixes();
+ while (allPrefixes.hasMoreElements()) {
+ String prefix = (String)allPrefixes.nextElement();
+ if (uri.equals(getURI(prefix))) {
+ prefixes.addElement(prefix);
+ }
+ }
+ return prefixes.elements();
+ }
+
+
+ /**
+ * Return an enumeration of all prefixes declared in this context.
+ *
+ * <p>The empty (default) prefix will be included in this
+ * enumeration; note that this behaviour differs from that of
+ * {@link #getPrefix} and {@link #getPrefixes}.</p>
+ *
+ * @return An enumeration of all prefixes declared in this
+ * context.
+ * @see #getPrefixes
+ * @see #getURI
+ */
+ public Enumeration getDeclaredPrefixes ()
+ {
+ return currentContext.getDeclaredPrefixes();
+ }
+
+ /**
+ * Controls whether namespace declaration attributes are placed
+ * into the {@link #NSDECL NSDECL} namespace
+ * by {@link #processName processName()}. This may only be
+ * changed before any contexts have been pushed.
+ *
+ * @since SAX 2.1alpha
+ *
+ * @exception IllegalStateException when attempting to set this
+ * after any context has been pushed.
+ */
+ public void setNamespaceDeclUris (boolean value)
+ {
+ if (contextPos != 0)
+ throw new IllegalStateException ();
+ if (value == namespaceDeclUris)
+ return;
+ namespaceDeclUris = value;
+ if (value)
+ currentContext.declarePrefix ("xmlns", NSDECL);
+ else {
+ contexts[contextPos] = currentContext = new Context();
+ currentContext.declarePrefix("xml", XMLNS);
+ }
+ }
+
+ /**
+ * Returns true if namespace declaration attributes are placed into
+ * a namespace. This behavior is not the default.
+ *
+ * @since SAX 2.1alpha
+ */
+ public boolean isNamespaceDeclUris ()
+ { return namespaceDeclUris; }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ private Context contexts[];
+ private Context currentContext;
+ private int contextPos;
+ private boolean namespaceDeclUris;
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal classes.
+ ////////////////////////////////////////////////////////////////////
+
+ /**
+ * Internal class for a single Namespace context.
+ *
+ * <p>This module caches and reuses Namespace contexts,
+ * so the number allocated
+ * will be equal to the element depth of the document, not to the total
+ * number of elements (i.e. 5-10 rather than tens of thousands).
+ * Also, data structures used to represent contexts are shared when
+ * possible (child contexts without declarations) to further reduce
+ * the amount of memory that's consumed.
+ * </p>
+ */
+ final class Context {
+
+ /**
+ * Create the root-level Namespace context.
+ */
+ Context ()
+ {
+ copyTables();
+ }
+
+
+ /**
+ * (Re)set the parent of this Namespace context.
+ * The context must either have been freshly constructed,
+ * or must have been cleared.
+ *
+ * @param context The parent Namespace context object.
+ */
+ void setParent (Context parent)
+ {
+ this.parent = parent;
+ declarations = null;
+ prefixTable = parent.prefixTable;
+ uriTable = parent.uriTable;
+ elementNameTable = parent.elementNameTable;
+ attributeNameTable = parent.attributeNameTable;
+ defaultNS = parent.defaultNS;
+ declSeen = false;
+ declsOK = true;
+ }
+
+ /**
+ * Makes associated state become collectible,
+ * invalidating this context.
+ * {@link #setParent} must be called before
+ * this context may be used again.
+ */
+ void clear ()
+ {
+ parent = null;
+ prefixTable = null;
+ uriTable = null;
+ elementNameTable = null;
+ attributeNameTable = null;
+ defaultNS = null;
+ }
+
+
+ /**
+ * Declare a Namespace prefix for this context.
+ *
+ * @param prefix The prefix to declare.
+ * @param uri The associated Namespace URI.
+ * @see org.xml.sax.helpers.NamespaceSupport#declarePrefix
+ */
+ void declarePrefix (String prefix, String uri)
+ {
+ // Lazy processing...
+ if (!declsOK)
+ throw new IllegalStateException (
+ "can't declare any more prefixes in this context");
+ if (!declSeen) {
+ copyTables();
+ }
+ if (declarations == null) {
+ declarations = new Vector();
+ }
+
+ prefix = prefix.intern();
+ uri = uri.intern();
+ if ("".equals(prefix)) {
+ if ("".equals(uri)) {
+ defaultNS = null;
+ } else {
+ defaultNS = uri;
+ }
+ } else {
+ prefixTable.put(prefix, uri);
+ uriTable.put(uri, prefix); // may wipe out another prefix
+ }
+ declarations.addElement(prefix);
+ }
+
+
+ /**
+ * Process an XML qualified name in this context.
+ *
+ * @param qName The XML qualified name.
+ * @param isAttribute true if this is an attribute name.
+ * @return An array of three strings containing the
+ * URI part (or empty string), the local part,
+ * and the raw name, all internalized, or null
+ * if there is an undeclared prefix.
+ * @see org.xml.sax.helpers.NamespaceSupport#processName
+ */
+ String [] processName (String qName, boolean isAttribute)
+ {
+ String name[];
+ Hashtable table;
+
+ // detect errors in call sequence
+ declsOK = false;
+
+ // Select the appropriate table.
+ if (isAttribute) {
+ table = attributeNameTable;
+ } else {
+ table = elementNameTable;
+ }
+
+ // Start by looking in the cache, and
+ // return immediately if the name
+ // is already known in this content
+ name = (String[])table.get(qName);
+ if (name != null) {
+ return name;
+ }
+
+ // We haven't seen this name in this
+ // context before. Maybe in the parent
+ // context, but we can't assume prefix
+ // bindings are the same.
+ name = new String[3];
+ name[2] = qName.intern();
+ int index = qName.indexOf(':');
+
+
+ // No prefix.
+ if (index == -1) {
+ if (isAttribute) {
+ if (qName == "xmlns" && namespaceDeclUris)
+ name[0] = NSDECL;
+ else
+ name[0] = "";
+ } else if (defaultNS == null) {
+ name[0] = "";
+ } else {
+ name[0] = defaultNS;
+ }
+ name[1] = name[2];
+ }
+
+ // Prefix
+ else {
+ String prefix = qName.substring(0, index);
+ String local = qName.substring(index+1);
+ String uri;
+ if ("".equals(prefix)) {
+ uri = defaultNS;
+ } else {
+ uri = (String)prefixTable.get(prefix);
+ }
+ if (uri == null
+ || (!isAttribute && "xmlns".equals (prefix))) {
+ return null;
+ }
+ name[0] = uri;
+ name[1] = local.intern();
+ }
+
+ // Save in the cache for future use.
+ // (Could be shared with parent context...)
+ table.put(name[2], name);
+ return name;
+ }
+
+
+ /**
+ * Look up the URI associated with a prefix in this context.
+ *
+ * @param prefix The prefix to look up.
+ * @return The associated Namespace URI, or null if none is
+ * declared.
+ * @see org.xml.sax.helpers.NamespaceSupport#getURI
+ */
+ String getURI (String prefix)
+ {
+ if ("".equals(prefix)) {
+ return defaultNS;
+ } else if (prefixTable == null) {
+ return null;
+ } else {
+ return (String)prefixTable.get(prefix);
+ }
+ }
+
+
+ /**
+ * Look up one of the prefixes associated with a URI in this context.
+ *
+ * <p>Since many prefixes may be mapped to the same URI,
+ * the return value may be unreliable.</p>
+ *
+ * @param uri The URI to look up.
+ * @return The associated prefix, or null if none is declared.
+ * @see org.xml.sax.helpers.NamespaceSupport#getPrefix
+ */
+ String getPrefix (String uri)
+ {
+ if (uriTable == null) {
+ return null;
+ } else {
+ return (String)uriTable.get(uri);
+ }
+ }
+
+
+ /**
+ * Return an enumeration of prefixes declared in this context.
+ *
+ * @return An enumeration of prefixes (possibly empty).
+ * @see org.xml.sax.helpers.NamespaceSupport#getDeclaredPrefixes
+ */
+ Enumeration getDeclaredPrefixes ()
+ {
+ if (declarations == null) {
+ return EMPTY_ENUMERATION;
+ } else {
+ return declarations.elements();
+ }
+ }
+
+
+ /**
+ * Return an enumeration of all prefixes currently in force.
+ *
+ * <p>The default prefix, if in force, is <em>not</em>
+ * returned, and will have to be checked for separately.</p>
+ *
+ * @return An enumeration of prefixes (never empty).
+ * @see org.xml.sax.helpers.NamespaceSupport#getPrefixes
+ */
+ Enumeration getPrefixes ()
+ {
+ if (prefixTable == null) {
+ return EMPTY_ENUMERATION;
+ } else {
+ return prefixTable.keys();
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////
+ // Internal methods.
+ ////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Copy on write for the internal tables in this context.
+ *
+ * <p>This class is optimized for the normal case where most
+ * elements do not contain Namespace declarations.</p>
+ */
+ private void copyTables ()
+ {
+ if (prefixTable != null) {
+ prefixTable = (Hashtable)prefixTable.clone();
+ } else {
+ prefixTable = new Hashtable();
+ }
+ if (uriTable != null) {
+ uriTable = (Hashtable)uriTable.clone();
+ } else {
+ uriTable = new Hashtable();
+ }
+ elementNameTable = new Hashtable();
+ attributeNameTable = new Hashtable();
+ declSeen = true;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////
+ // Protected state.
+ ////////////////////////////////////////////////////////////////
+
+ Hashtable prefixTable;
+ Hashtable uriTable;
+ Hashtable elementNameTable;
+ Hashtable attributeNameTable;
+ String defaultNS = null;
+ boolean declsOK = true;
+
+
+
+ ////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////
+
+ private Vector declarations = null;
+ private boolean declSeen = false;
+ private Context parent = null;
+ }
+}
+
+// end of NamespaceSupport.java
diff --git a/src/org/xml/sax/helpers/NewInstance.java b/src/org/xml/sax/helpers/NewInstance.java
new file mode 100644
index 0000000..6fc34d6
--- /dev/null
+++ b/src/org/xml/sax/helpers/NewInstance.java
@@ -0,0 +1,79 @@
+// NewInstance.java - create a new instance of a class by name.
+// http://www.saxproject.org
+// Written by Edwin Goei, edwingo at apache.org
+// and by David Brownell, dbrownell at users.sourceforge.net
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: NewInstance.java,v 1.4 2002/01/30 20:52:27 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Create a new instance of a class by name.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class contains a static method for creating an instance of a
+ * class from an explicit class name. It tries to use the thread's context
+ * ClassLoader if possible and falls back to using
+ * Class.forName(String).</p>
+ *
+ * <p>This code is designed to compile and run on JDK version 1.1 and later
+ * including versions of Java 2.</p>
+ *
+ * @author Edwin Goei, David Brownell
+ * @version 2.0.1 (sax2r2)
+ */
+class NewInstance {
+
+ /**
+ * Creates a new instance of the specified class name
+ *
+ * Package private so this code is not exposed at the API level.
+ */
+ static Object newInstance (ClassLoader classLoader, String className)
+ throws ClassNotFoundException, IllegalAccessException,
+ InstantiationException
+ {
+ Class driverClass;
+ if (classLoader == null) {
+ driverClass = Class.forName(className);
+ } else {
+ driverClass = classLoader.loadClass(className);
+ }
+ return driverClass.newInstance();
+ }
+
+ /**
+ * Figure out which ClassLoader to use. For JDK 1.2 and later use
+ * the context ClassLoader.
+ */
+ static ClassLoader getClassLoader ()
+ {
+ Method m = null;
+
+ try {
+ m = Thread.class.getMethod("getContextClassLoader", null);
+ } catch (NoSuchMethodException e) {
+ // Assume that we are running JDK 1.1, use the current ClassLoader
+ return NewInstance.class.getClassLoader();
+ }
+
+ try {
+ return (ClassLoader) m.invoke(Thread.currentThread(), null);
+ } catch (IllegalAccessException e) {
+ // assert(false)
+ throw new UnknownError(e.getMessage());
+ } catch (InvocationTargetException e) {
+ // assert(e.getTargetException() instanceof SecurityException)
+ throw new UnknownError(e.getMessage());
+ }
+ }
+}
diff --git a/src/org/xml/sax/helpers/ParserAdapter.java b/src/org/xml/sax/helpers/ParserAdapter.java
new file mode 100644
index 0000000..ceff681
--- /dev/null
+++ b/src/org/xml/sax/helpers/ParserAdapter.java
@@ -0,0 +1,1046 @@
+// ParserAdapter.java - adapt a SAX1 Parser to a SAX2 XMLReader.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: ParserAdapter.java,v 1.16 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.xml.sax.Parser; // deprecated
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.AttributeList; // deprecated
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.DocumentHandler; // deprecated
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+
+/**
+ * Adapt a SAX1 Parser as a SAX2 XMLReader.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class wraps a SAX1 {@link org.xml.sax.Parser Parser}
+ * and makes it act as a SAX2 {@link org.xml.sax.XMLReader XMLReader},
+ * with feature, property, and Namespace support. Note
+ * that it is not possible to report {@link org.xml.sax.ContentHandler#skippedEntity
+ * skippedEntity} events, since SAX1 does not make that information available.</p>
+ *
+ * <p>This adapter does not test for duplicate Namespace-qualified
+ * attribute names.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.XMLReaderAdapter
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.Parser
+ */
+public class ParserAdapter implements XMLReader, DocumentHandler
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constructors.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Construct a new parser adapter.
+ *
+ * <p>Use the "org.xml.sax.parser" property to locate the
+ * embedded SAX1 driver.</p>
+ *
+ * @exception SAXException If the embedded driver
+ * cannot be instantiated or if the
+ * org.xml.sax.parser property is not specified.
+ */
+ public ParserAdapter ()
+ throws SAXException
+ {
+ super();
+
+ String driver = System.getProperty("org.xml.sax.parser");
+
+ try {
+ setup(ParserFactory.makeParser());
+ } catch (ClassNotFoundException e1) {
+ throw new
+ SAXException("Cannot find SAX1 driver class " +
+ driver, e1);
+ } catch (IllegalAccessException e2) {
+ throw new
+ SAXException("SAX1 driver class " +
+ driver +
+ " found but cannot be loaded", e2);
+ } catch (InstantiationException e3) {
+ throw new
+ SAXException("SAX1 driver class " +
+ driver +
+ " loaded but cannot be instantiated", e3);
+ } catch (ClassCastException e4) {
+ throw new
+ SAXException("SAX1 driver class " +
+ driver +
+ " does not implement org.xml.sax.Parser");
+ } catch (NullPointerException e5) {
+ throw new
+ SAXException("System property org.xml.sax.parser not specified");
+ }
+ }
+
+
+ /**
+ * Construct a new parser adapter.
+ *
+ * <p>Note that the embedded parser cannot be changed once the
+ * adapter is created; to embed a different parser, allocate
+ * a new ParserAdapter.</p>
+ *
+ * @param parser The SAX1 parser to embed.
+ * @exception java.lang.NullPointerException If the parser parameter
+ * is null.
+ */
+ public ParserAdapter (Parser parser)
+ {
+ super();
+ setup(parser);
+ }
+
+
+ /**
+ * Internal setup method.
+ *
+ * @param parser The embedded parser.
+ * @exception java.lang.NullPointerException If the parser parameter
+ * is null.
+ */
+ private void setup (Parser parser)
+ {
+ if (parser == null) {
+ throw new
+ NullPointerException("Parser argument must not be null");
+ }
+ this.parser = parser;
+ atts = new AttributesImpl();
+ nsSupport = new NamespaceSupport();
+ attAdapter = new AttributeListAdapter();
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.XMLReader.
+ ////////////////////////////////////////////////////////////////////
+
+
+ //
+ // Internal constants for the sake of convenience.
+ //
+ private final static String FEATURES = "http://xml.org/sax/features/";
+ private final static String NAMESPACES = FEATURES + "namespaces";
+ private final static String NAMESPACE_PREFIXES = FEATURES + "namespace-prefixes";
+ private final static String XMLNS_URIs = FEATURES + "xmlns-uris";
+
+
+ /**
+ * Set a feature flag for the parser.
+ *
+ * <p>The only features recognized are namespaces and
+ * namespace-prefixes.</p>
+ *
+ * @param name The feature name, as a complete URI.
+ * @param value The requested feature value.
+ * @exception SAXNotRecognizedException If the feature
+ * can't be assigned or retrieved.
+ * @exception SAXNotSupportedException If the feature
+ * can't be assigned that value.
+ * @see org.xml.sax.XMLReader#setFeature
+ */
+ public void setFeature (String name, boolean value)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (name.equals(NAMESPACES)) {
+ checkNotParsing("feature", name);
+ namespaces = value;
+ if (!namespaces && !prefixes) {
+ prefixes = true;
+ }
+ } else if (name.equals(NAMESPACE_PREFIXES)) {
+ checkNotParsing("feature", name);
+ prefixes = value;
+ if (!prefixes && !namespaces) {
+ namespaces = true;
+ }
+ } else if (name.equals(XMLNS_URIs)) {
+ checkNotParsing("feature", name);
+ uris = value;
+ } else {
+ throw new SAXNotRecognizedException("Feature: " + name);
+ }
+ }
+
+
+ /**
+ * Check a parser feature flag.
+ *
+ * <p>The only features recognized are namespaces and
+ * namespace-prefixes.</p>
+ *
+ * @param name The feature name, as a complete URI.
+ * @return The current feature value.
+ * @exception SAXNotRecognizedException If the feature
+ * value can't be assigned or retrieved.
+ * @exception SAXNotSupportedException If the
+ * feature is not currently readable.
+ * @see org.xml.sax.XMLReader#setFeature
+ */
+ public boolean getFeature (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (name.equals(NAMESPACES)) {
+ return namespaces;
+ } else if (name.equals(NAMESPACE_PREFIXES)) {
+ return prefixes;
+ } else if (name.equals(XMLNS_URIs)) {
+ return uris;
+ } else {
+ throw new SAXNotRecognizedException("Feature: " + name);
+ }
+ }
+
+
+ /**
+ * Set a parser property.
+ *
+ * <p>No properties are currently recognized.</p>
+ *
+ * @param name The property name.
+ * @param value The property value.
+ * @exception SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved.
+ * @exception SAXNotSupportedException If the property
+ * can't be assigned that value.
+ * @see org.xml.sax.XMLReader#setProperty
+ */
+ public void setProperty (String name, Object value)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ throw new SAXNotRecognizedException("Property: " + name);
+ }
+
+
+ /**
+ * Get a parser property.
+ *
+ * <p>No properties are currently recognized.</p>
+ *
+ * @param name The property name.
+ * @return The property value.
+ * @exception SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved.
+ * @exception SAXNotSupportedException If the property
+ * value is not currently readable.
+ * @see org.xml.sax.XMLReader#getProperty
+ */
+ public Object getProperty (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ throw new SAXNotRecognizedException("Property: " + name);
+ }
+
+
+ /**
+ * Set the entity resolver.
+ *
+ * @param resolver The new entity resolver.
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ */
+ public void setEntityResolver (EntityResolver resolver)
+ {
+ entityResolver = resolver;
+ }
+
+
+ /**
+ * Return the current entity resolver.
+ *
+ * @return The current entity resolver, or null if none was supplied.
+ * @see org.xml.sax.XMLReader#getEntityResolver
+ */
+ public EntityResolver getEntityResolver ()
+ {
+ return entityResolver;
+ }
+
+
+ /**
+ * Set the DTD handler.
+ *
+ * @param handler the new DTD handler
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ */
+ public void setDTDHandler (DTDHandler handler)
+ {
+ dtdHandler = handler;
+ }
+
+
+ /**
+ * Return the current DTD handler.
+ *
+ * @return the current DTD handler, or null if none was supplied
+ * @see org.xml.sax.XMLReader#getEntityResolver
+ */
+ public DTDHandler getDTDHandler ()
+ {
+ return dtdHandler;
+ }
+
+
+ /**
+ * Set the content handler.
+ *
+ * @param handler the new content handler
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ */
+ public void setContentHandler (ContentHandler handler)
+ {
+ contentHandler = handler;
+ }
+
+
+ /**
+ * Return the current content handler.
+ *
+ * @return The current content handler, or null if none was supplied.
+ * @see org.xml.sax.XMLReader#getEntityResolver
+ */
+ public ContentHandler getContentHandler ()
+ {
+ return contentHandler;
+ }
+
+
+ /**
+ * Set the error handler.
+ *
+ * @param handler The new error handler.
+ * @see org.xml.sax.XMLReader#setEntityResolver
+ */
+ public void setErrorHandler (ErrorHandler handler)
+ {
+ errorHandler = handler;
+ }
+
+
+ /**
+ * Return the current error handler.
+ *
+ * @return The current error handler, or null if none was supplied.
+ * @see org.xml.sax.XMLReader#getEntityResolver
+ */
+ public ErrorHandler getErrorHandler ()
+ {
+ return errorHandler;
+ }
+
+
+ /**
+ * Parse an XML document.
+ *
+ * @param systemId The absolute URL of the document.
+ * @exception java.io.IOException If there is a problem reading
+ * the raw content of the document.
+ * @exception SAXException If there is a problem
+ * processing the document.
+ * @see #parse(org.xml.sax.InputSource)
+ * @see org.xml.sax.Parser#parse(java.lang.String)
+ */
+ public void parse (String systemId)
+ throws IOException, SAXException
+ {
+ parse(new InputSource(systemId));
+ }
+
+
+ /**
+ * Parse an XML document.
+ *
+ * @param input An input source for the document.
+ * @exception java.io.IOException If there is a problem reading
+ * the raw content of the document.
+ * @exception SAXException If there is a problem
+ * processing the document.
+ * @see #parse(java.lang.String)
+ * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
+ */
+ public void parse (InputSource input)
+ throws IOException, SAXException
+ {
+ if (parsing) {
+ throw new SAXException("Parser is already in use");
+ }
+ setupParser();
+ parsing = true;
+ try {
+ parser.parse(input);
+ } finally {
+ parsing = false;
+ }
+ parsing = false;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.DocumentHandler.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 document locator event.
+ *
+ * @param locator A document locator.
+ * @see org.xml.sax.ContentHandler#setDocumentLocator
+ */
+ public void setDocumentLocator (Locator locator)
+ {
+ this.locator = locator;
+ if (contentHandler != null) {
+ contentHandler.setDocumentLocator(locator);
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 start document event.
+ *
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#startDocument
+ */
+ public void startDocument ()
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.startDocument();
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 end document event.
+ *
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#endDocument
+ */
+ public void endDocument ()
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.endDocument();
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 startElement event.
+ *
+ * <p>If necessary, perform Namespace processing.</p>
+ *
+ * @param qName The qualified (prefixed) name.
+ * @param qAtts The XML attribute list (with qnames).
+ * @exception SAXException The client may raise a
+ * processing exception.
+ */
+ public void startElement (String qName, AttributeList qAtts)
+ throws SAXException
+ {
+ // These are exceptions from the
+ // first pass; they should be
+ // ignored if there's a second pass,
+ // but reported otherwise.
+ Vector exceptions = null;
+
+ // If we're not doing Namespace
+ // processing, dispatch this quickly.
+ if (!namespaces) {
+ if (contentHandler != null) {
+ attAdapter.setAttributeList(qAtts);
+ contentHandler.startElement("", "", qName.intern(),
+ attAdapter);
+ }
+ return;
+ }
+
+
+ // OK, we're doing Namespace processing.
+ nsSupport.pushContext();
+ int length = qAtts.getLength();
+
+ // First pass: handle NS decls
+ for (int i = 0; i < length; i++) {
+ String attQName = qAtts.getName(i);
+
+ if (!attQName.startsWith("xmlns"))
+ continue;
+ // Could be a declaration...
+ String prefix;
+ int n = attQName.indexOf(':');
+
+ // xmlns=...
+ if (n == -1 && attQName.length () == 5) {
+ prefix = "";
+ } else if (n != 5) {
+ // XML namespaces spec doesn't discuss "xmlnsf:oo"
+ // (and similarly named) attributes ... at most, warn
+ continue;
+ } else // xmlns:foo=...
+ prefix = attQName.substring(n+1);
+
+ String value = qAtts.getValue(i);
+ if (!nsSupport.declarePrefix(prefix, value)) {
+ reportError("Illegal Namespace prefix: " + prefix);
+ continue;
+ }
+ if (contentHandler != null)
+ contentHandler.startPrefixMapping(prefix, value);
+ }
+
+ // Second pass: copy all relevant
+ // attributes into the SAX2 AttributeList
+ // using updated prefix bindings
+ atts.clear();
+ for (int i = 0; i < length; i++) {
+ String attQName = qAtts.getName(i);
+ String type = qAtts.getType(i);
+ String value = qAtts.getValue(i);
+
+ // Declaration?
+ if (attQName.startsWith("xmlns")) {
+ String prefix;
+ int n = attQName.indexOf(':');
+
+ if (n == -1 && attQName.length () == 5) {
+ prefix = "";
+ } else if (n != 5) {
+ // XML namespaces spec doesn't discuss "xmlnsf:oo"
+ // (and similarly named) attributes ... ignore
+ prefix = null;
+ } else {
+ prefix = attQName.substring(6);
+ }
+ // Yes, decl: report or prune
+ if (prefix != null) {
+ if (prefixes) {
+ if (uris)
+ // note funky case: localname can be null
+ // when declaring the default prefix, and
+ // yet the uri isn't null.
+ atts.addAttribute (nsSupport.XMLNS, prefix,
+ attQName.intern(), type, value);
+ else
+ atts.addAttribute ("", "",
+ attQName.intern(), type, value);
+ }
+ continue;
+ }
+ }
+
+ // Not a declaration -- report
+ try {
+ String attName[] = processName(attQName, true, true);
+ atts.addAttribute(attName[0], attName[1], attName[2],
+ type, value);
+ } catch (SAXException e) {
+ if (exceptions == null)
+ exceptions = new Vector();
+ exceptions.addElement(e);
+ atts.addAttribute("", attQName, attQName, type, value);
+ }
+ }
+
+ // now handle the deferred exception reports
+ if (exceptions != null && errorHandler != null) {
+ for (int i = 0; i < exceptions.size(); i++)
+ errorHandler.error((SAXParseException)
+ (exceptions.elementAt(i)));
+ }
+
+ // OK, finally report the event.
+ if (contentHandler != null) {
+ String name[] = processName(qName, false, false);
+ contentHandler.startElement(name[0], name[1], name[2], atts);
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 end element event.
+ *
+ * @param qName The qualified (prefixed) name.
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#endElement
+ */
+ public void endElement (String qName)
+ throws SAXException
+ {
+ // If we're not doing Namespace
+ // processing, dispatch this quickly.
+ if (!namespaces) {
+ if (contentHandler != null) {
+ contentHandler.endElement("", "", qName.intern());
+ }
+ return;
+ }
+
+ // Split the name.
+ String names[] = processName(qName, false, false);
+ if (contentHandler != null) {
+ contentHandler.endElement(names[0], names[1], names[2]);
+ Enumeration prefixes = nsSupport.getDeclaredPrefixes();
+ while (prefixes.hasMoreElements()) {
+ String prefix = (String)prefixes.nextElement();
+ contentHandler.endPrefixMapping(prefix);
+ }
+ }
+ nsSupport.popContext();
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 characters event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use.
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#characters
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.characters(ch, start, length);
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 ignorable whitespace event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use.
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#ignorableWhitespace
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.ignorableWhitespace(ch, start, length);
+ }
+ }
+
+
+ /**
+ * Adapter implementation method; do not call.
+ * Adapt a SAX1 processing instruction event.
+ *
+ * @param target The processing instruction target.
+ * @param data The remainder of the processing instruction
+ * @exception SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.DocumentHandler#processingInstruction
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.processingInstruction(target, data);
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal utility methods.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Initialize the parser before each run.
+ */
+ private void setupParser ()
+ {
+ // catch an illegal "nonsense" state.
+ if (!prefixes && !namespaces)
+ throw new IllegalStateException ();
+
+ nsSupport.reset();
+ if (uris)
+ nsSupport.setNamespaceDeclUris (true);
+
+ if (entityResolver != null) {
+ parser.setEntityResolver(entityResolver);
+ }
+ if (dtdHandler != null) {
+ parser.setDTDHandler(dtdHandler);
+ }
+ if (errorHandler != null) {
+ parser.setErrorHandler(errorHandler);
+ }
+ parser.setDocumentHandler(this);
+ locator = null;
+ }
+
+
+ /**
+ * Process a qualified (prefixed) name.
+ *
+ * <p>If the name has an undeclared prefix, use only the qname
+ * and make an ErrorHandler.error callback in case the app is
+ * interested.</p>
+ *
+ * @param qName The qualified (prefixed) name.
+ * @param isAttribute true if this is an attribute name.
+ * @return The name split into three parts.
+ * @exception SAXException The client may throw
+ * an exception if there is an error callback.
+ */
+ private String [] processName (String qName, boolean isAttribute,
+ boolean useException)
+ throws SAXException
+ {
+ String parts[] = nsSupport.processName(qName, nameParts,
+ isAttribute);
+ if (parts == null) {
+ if (useException)
+ throw makeException("Undeclared prefix: " + qName);
+ reportError("Undeclared prefix: " + qName);
+ parts = new String[3];
+ parts[0] = parts[1] = "";
+ parts[2] = qName.intern();
+ }
+ return parts;
+ }
+
+
+ /**
+ * Report a non-fatal error.
+ *
+ * @param message The error message.
+ * @exception SAXException The client may throw
+ * an exception.
+ */
+ void reportError (String message)
+ throws SAXException
+ {
+ if (errorHandler != null)
+ errorHandler.error(makeException(message));
+ }
+
+
+ /**
+ * Construct an exception for the current context.
+ *
+ * @param message The error message.
+ */
+ private SAXParseException makeException (String message)
+ {
+ if (locator != null) {
+ return new SAXParseException(message, locator);
+ } else {
+ return new SAXParseException(message, null, null, -1, -1);
+ }
+ }
+
+
+ /**
+ * Throw an exception if we are parsing.
+ *
+ * <p>Use this method to detect illegal feature or
+ * property changes.</p>
+ *
+ * @param type The type of thing (feature or property).
+ * @param name The feature or property name.
+ * @exception SAXNotSupportedException If a
+ * document is currently being parsed.
+ */
+ private void checkNotParsing (String type, String name)
+ throws SAXNotSupportedException
+ {
+ if (parsing) {
+ throw new SAXNotSupportedException("Cannot change " +
+ type + ' ' +
+ name + " while parsing");
+
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ private NamespaceSupport nsSupport;
+ private AttributeListAdapter attAdapter;
+
+ private boolean parsing = false;
+ private String nameParts[] = new String[3];
+
+ private Parser parser = null;
+
+ private AttributesImpl atts = null;
+
+ // Features
+ private boolean namespaces = true;
+ private boolean prefixes = false;
+ private boolean uris = false;
+
+ // Properties
+
+ // Handlers
+ Locator locator;
+
+ EntityResolver entityResolver = null;
+ DTDHandler dtdHandler = null;
+ ContentHandler contentHandler = null;
+ ErrorHandler errorHandler = null;
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Inner class to wrap an AttributeList when not doing NS proc.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
+ *
+ * <p>This class is in the Public Domain, and comes with NO
+ * WARRANTY of any kind.</p>
+ *
+ * <p>This wrapper class is used only when Namespace support
+ * is disabled -- it provides pretty much a direct mapping
+ * from SAX1 to SAX2, except that names and types are
+ * interned whenever requested.</p>
+ */
+ final class AttributeListAdapter implements Attributes
+ {
+
+ /**
+ * Construct a new adapter.
+ */
+ AttributeListAdapter ()
+ {
+ }
+
+
+ /**
+ * Set the embedded AttributeList.
+ *
+ * <p>This method must be invoked before any of the others
+ * can be used.</p>
+ *
+ * @param The SAX1 attribute list (with qnames).
+ */
+ void setAttributeList (AttributeList qAtts)
+ {
+ this.qAtts = qAtts;
+ }
+
+
+ /**
+ * Return the length of the attribute list.
+ *
+ * @return The number of attributes in the list.
+ * @see org.xml.sax.Attributes#getLength
+ */
+ public int getLength ()
+ {
+ return qAtts.getLength();
+ }
+
+
+ /**
+ * Return the Namespace URI of the specified attribute.
+ *
+ * @param The attribute's index.
+ * @return Always the empty string.
+ * @see org.xml.sax.Attributes#getURI
+ */
+ public String getURI (int i)
+ {
+ return "";
+ }
+
+
+ /**
+ * Return the local name of the specified attribute.
+ *
+ * @param The attribute's index.
+ * @return Always the empty string.
+ * @see org.xml.sax.Attributes#getLocalName
+ */
+ public String getLocalName (int i)
+ {
+ return "";
+ }
+
+
+ /**
+ * Return the qualified (prefixed) name of the specified attribute.
+ *
+ * @param The attribute's index.
+ * @return The attribute's qualified name, internalized.
+ */
+ public String getQName (int i)
+ {
+ return qAtts.getName(i).intern();
+ }
+
+
+ /**
+ * Return the type of the specified attribute.
+ *
+ * @param The attribute's index.
+ * @return The attribute's type as an internalized string.
+ */
+ public String getType (int i)
+ {
+ return qAtts.getType(i).intern();
+ }
+
+
+ /**
+ * Return the value of the specified attribute.
+ *
+ * @param The attribute's index.
+ * @return The attribute's value.
+ */
+ public String getValue (int i)
+ {
+ return qAtts.getValue(i);
+ }
+
+
+ /**
+ * Look up an attribute index by Namespace name.
+ *
+ * @param uri The Namespace URI or the empty string.
+ * @param localName The local name.
+ * @return The attributes index, or -1 if none was found.
+ * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
+ */
+ public int getIndex (String uri, String localName)
+ {
+ return -1;
+ }
+
+
+ /**
+ * Look up an attribute index by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attributes index, or -1 if none was found.
+ * @see org.xml.sax.Attributes#getIndex(java.lang.String)
+ */
+ public int getIndex (String qName)
+ {
+ int max = atts.getLength();
+ for (int i = 0; i < max; i++) {
+ if (qAtts.getName(i).equals(qName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Look up the type of an attribute by Namespace name.
+ *
+ * @param uri The Namespace URI
+ * @param localName The local name.
+ * @return The attribute's type as an internalized string.
+ */
+ public String getType (String uri, String localName)
+ {
+ return null;
+ }
+
+
+ /**
+ * Look up the type of an attribute by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attribute's type as an internalized string.
+ */
+ public String getType (String qName)
+ {
+ return qAtts.getType(qName).intern();
+ }
+
+
+ /**
+ * Look up the value of an attribute by Namespace name.
+ *
+ * @param uri The Namespace URI
+ * @param localName The local name.
+ * @return The attribute's value.
+ */
+ public String getValue (String uri, String localName)
+ {
+ return null;
+ }
+
+
+ /**
+ * Look up the value of an attribute by qualified (prefixed) name.
+ *
+ * @param qName The qualified name.
+ * @return The attribute's value.
+ */
+ public String getValue (String qName)
+ {
+ return qAtts.getValue(qName);
+ }
+
+ private AttributeList qAtts;
+ }
+}
+
+// end of ParserAdapter.java
diff --git a/src/org/xml/sax/helpers/ParserFactory.java b/src/org/xml/sax/helpers/ParserFactory.java
new file mode 100644
index 0000000..61eb72d
--- /dev/null
+++ b/src/org/xml/sax/helpers/ParserFactory.java
@@ -0,0 +1,129 @@
+// SAX parser factory.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: ParserFactory.java,v 1.7 2002/01/30 20:52:36 dbrownell Exp $
+
+package org.xml.sax.helpers;
+
+import java.lang.ClassNotFoundException;
+import java.lang.IllegalAccessException;
+import java.lang.InstantiationException;
+import java.lang.SecurityException;
+import java.lang.ClassCastException;
+
+import org.xml.sax.Parser;
+
+
+/**
+ * Java-specific class for dynamically loading SAX parsers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p><strong>Note:</strong> This class is designed to work with the now-deprecated
+ * SAX1 {@link org.xml.sax.Parser Parser} class. SAX2 applications should use
+ * {@link org.xml.sax.helpers.XMLReaderFactory XMLReaderFactory} instead.</p>
+ *
+ * <p>ParserFactory is not part of the platform-independent definition
+ * of SAX; it is an additional convenience class designed
+ * specifically for Java XML application writers. SAX applications
+ * can use the static methods in this class to allocate a SAX parser
+ * dynamically at run-time based either on the value of the
+ * `org.xml.sax.parser' system property or on a string containing the class
+ * name.</p>
+ *
+ * <p>Note that the application still requires an XML parser that
+ * implements SAX1.</p>
+ *
+ * @deprecated This class works with the deprecated
+ * {@link org.xml.sax.Parser Parser}
+ * interface.
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ */
+public class ParserFactory {
+
+
+ /**
+ * Private null constructor.
+ */
+ private ParserFactory ()
+ {
+ }
+
+
+ /**
+ * Create a new SAX parser using the `org.xml.sax.parser' system property.
+ *
+ * <p>The named class must exist and must implement the
+ * {@link org.xml.sax.Parser Parser} interface.</p>
+ *
+ * @exception java.lang.NullPointerException There is no value
+ * for the `org.xml.sax.parser' system property.
+ * @exception java.lang.ClassNotFoundException The SAX parser
+ * class was not found (check your CLASSPATH).
+ * @exception IllegalAccessException The SAX parser class was
+ * found, but you do not have permission to load
+ * it.
+ * @exception InstantiationException The SAX parser class was
+ * found but could not be instantiated.
+ * @exception java.lang.ClassCastException The SAX parser class
+ * was found and instantiated, but does not implement
+ * org.xml.sax.Parser.
+ * @see #makeParser(java.lang.String)
+ * @see org.xml.sax.Parser
+ */
+ public static Parser makeParser ()
+ throws ClassNotFoundException,
+ IllegalAccessException,
+ InstantiationException,
+ NullPointerException,
+ ClassCastException
+ {
+ String className = System.getProperty("org.xml.sax.parser");
+ if (className == null) {
+ throw new NullPointerException("No value for sax.parser property");
+ } else {
+ return makeParser(className);
+ }
+ }
+
+
+ /**
+ * Create a new SAX parser object using the class name provided.
+ *
+ * <p>The named class must exist and must implement the
+ * {@link org.xml.sax.Parser Parser} interface.</p>
+ *
+ * @param className A string containing the name of the
+ * SAX parser class.
+ * @exception java.lang.ClassNotFoundException The SAX parser
+ * class was not found (check your CLASSPATH).
+ * @exception IllegalAccessException The SAX parser class was
+ * found, but you do not have permission to load
+ * it.
+ * @exception InstantiationException The SAX parser class was
+ * found but could not be instantiated.
+ * @exception java.lang.ClassCastException The SAX parser class
+ * was found and instantiated, but does not implement
+ * org.xml.sax.Parser.
+ * @see #makeParser()
+ * @see org.xml.sax.Parser
+ */
+ public static Parser makeParser (String className)
+ throws ClassNotFoundException,
+ IllegalAccessException,
+ InstantiationException,
+ ClassCastException
+ {
+ return (Parser) NewInstance.newInstance (
+ NewInstance.getClassLoader (), className);
+ }
+
+}
+
diff --git a/src/org/xml/sax/helpers/XMLFilterImpl.java b/src/org/xml/sax/helpers/XMLFilterImpl.java
new file mode 100644
index 0000000..348b707
--- /dev/null
+++ b/src/org/xml/sax/helpers/XMLFilterImpl.java
@@ -0,0 +1,713 @@
+// XMLFilterImpl.java - base SAX2 filter implementation.
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: XMLFilterImpl.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.XMLFilter;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXNotRecognizedException;
+
+
+/**
+ * Base class for deriving an XML filter.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class is designed to sit between an {@link org.xml.sax.XMLReader
+ * XMLReader} and the client application's event handlers. By default, it
+ * does nothing but pass requests up to the reader and events
+ * on to the handlers unmodified, but subclasses can override
+ * specific methods to modify the event stream or the configuration
+ * requests as they pass through.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.XMLFilter
+ * @see org.xml.sax.XMLReader
+ * @see org.xml.sax.EntityResolver
+ * @see org.xml.sax.DTDHandler
+ * @see org.xml.sax.ContentHandler
+ * @see org.xml.sax.ErrorHandler
+ */
+public class XMLFilterImpl
+ implements XMLFilter, EntityResolver, DTDHandler, ContentHandler, ErrorHandler
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constructors.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Construct an empty XML filter, with no parent.
+ *
+ * <p>This filter will have no parent: you must assign a parent
+ * before you start a parse or do any configuration with
+ * setFeature or setProperty, unless you use this as a pure event
+ * consumer rather than as an {@link XMLReader}.</p>
+ *
+ * @see org.xml.sax.XMLReader#setFeature
+ * @see org.xml.sax.XMLReader#setProperty
+ * @see #setParent
+ */
+ public XMLFilterImpl ()
+ {
+ super();
+ }
+
+
+ /**
+ * Construct an XML filter with the specified parent.
+ *
+ * @see #setParent
+ * @see #getParent
+ */
+ public XMLFilterImpl (XMLReader parent)
+ {
+ super();
+ setParent(parent);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.XMLFilter.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set the parent reader.
+ *
+ * <p>This is the {@link org.xml.sax.XMLReader XMLReader} from which
+ * this filter will obtain its events and to which it will pass its
+ * configuration requests. The parent may itself be another filter.</p>
+ *
+ * <p>If there is no parent reader set, any attempt to parse
+ * or to set or get a feature or property will fail.</p>
+ *
+ * @param parent The parent XML reader.
+ * @see #getParent
+ */
+ public void setParent (XMLReader parent)
+ {
+ this.parent = parent;
+ }
+
+
+ /**
+ * Get the parent reader.
+ *
+ * @return The parent XML reader, or null if none is set.
+ * @see #setParent
+ */
+ public XMLReader getParent ()
+ {
+ return parent;
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.XMLReader.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set the value of a feature.
+ *
+ * <p>This will always fail if the parent is null.</p>
+ *
+ * @param name The feature name.
+ * @param value The requested feature value.
+ * @exception org.xml.sax.SAXNotRecognizedException If the feature
+ * value can't be assigned or retrieved from the parent.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * parent recognizes the feature name but
+ * cannot set the requested value.
+ */
+ public void setFeature (String name, boolean value)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (parent != null) {
+ parent.setFeature(name, value);
+ } else {
+ throw new SAXNotRecognizedException("Feature: " + name);
+ }
+ }
+
+
+ /**
+ * Look up the value of a feature.
+ *
+ * <p>This will always fail if the parent is null.</p>
+ *
+ * @param name The feature name.
+ * @return The current value of the feature.
+ * @exception org.xml.sax.SAXNotRecognizedException If the feature
+ * value can't be assigned or retrieved from the parent.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * parent recognizes the feature name but
+ * cannot determine its value at this time.
+ */
+ public boolean getFeature (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (parent != null) {
+ return parent.getFeature(name);
+ } else {
+ throw new SAXNotRecognizedException("Feature: " + name);
+ }
+ }
+
+
+ /**
+ * Set the value of a property.
+ *
+ * <p>This will always fail if the parent is null.</p>
+ *
+ * @param name The property name.
+ * @param value The requested property value.
+ * @exception org.xml.sax.SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved from the parent.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * parent recognizes the property name but
+ * cannot set the requested value.
+ */
+ public void setProperty (String name, Object value)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (parent != null) {
+ parent.setProperty(name, value);
+ } else {
+ throw new SAXNotRecognizedException("Property: " + name);
+ }
+ }
+
+
+ /**
+ * Look up the value of a property.
+ *
+ * @param name The property name.
+ * @return The current value of the property.
+ * @exception org.xml.sax.SAXNotRecognizedException If the property
+ * value can't be assigned or retrieved from the parent.
+ * @exception org.xml.sax.SAXNotSupportedException When the
+ * parent recognizes the property name but
+ * cannot determine its value at this time.
+ */
+ public Object getProperty (String name)
+ throws SAXNotRecognizedException, SAXNotSupportedException
+ {
+ if (parent != null) {
+ return parent.getProperty(name);
+ } else {
+ throw new SAXNotRecognizedException("Property: " + name);
+ }
+ }
+
+
+ /**
+ * Set the entity resolver.
+ *
+ * @param resolver The new entity resolver.
+ */
+ public void setEntityResolver (EntityResolver resolver)
+ {
+ entityResolver = resolver;
+ }
+
+
+ /**
+ * Get the current entity resolver.
+ *
+ * @return The current entity resolver, or null if none was set.
+ */
+ public EntityResolver getEntityResolver ()
+ {
+ return entityResolver;
+ }
+
+
+ /**
+ * Set the DTD event handler.
+ *
+ * @param handler the new DTD handler
+ */
+ public void setDTDHandler (DTDHandler handler)
+ {
+ dtdHandler = handler;
+ }
+
+
+ /**
+ * Get the current DTD event handler.
+ *
+ * @return The current DTD handler, or null if none was set.
+ */
+ public DTDHandler getDTDHandler ()
+ {
+ return dtdHandler;
+ }
+
+
+ /**
+ * Set the content event handler.
+ *
+ * @param handler the new content handler
+ */
+ public void setContentHandler (ContentHandler handler)
+ {
+ contentHandler = handler;
+ }
+
+
+ /**
+ * Get the content event handler.
+ *
+ * @return The current content handler, or null if none was set.
+ */
+ public ContentHandler getContentHandler ()
+ {
+ return contentHandler;
+ }
+
+
+ /**
+ * Set the error event handler.
+ *
+ * @param handler the new error handler
+ */
+ public void setErrorHandler (ErrorHandler handler)
+ {
+ errorHandler = handler;
+ }
+
+
+ /**
+ * Get the current error event handler.
+ *
+ * @return The current error handler, or null if none was set.
+ */
+ public ErrorHandler getErrorHandler ()
+ {
+ return errorHandler;
+ }
+
+
+ /**
+ * Parse a document.
+ *
+ * @param input The input source for the document entity.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ */
+ public void parse (InputSource input)
+ throws SAXException, IOException
+ {
+ setupParse();
+ parent.parse(input);
+ }
+
+
+ /**
+ * Parse a document.
+ *
+ * @param systemId The system identifier as a fully-qualified URI.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @exception java.io.IOException An IO exception from the parser,
+ * possibly from a byte stream or character stream
+ * supplied by the application.
+ */
+ public void parse (String systemId)
+ throws SAXException, IOException
+ {
+ parse(new InputSource(systemId));
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.EntityResolver.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Filter an external entity resolution.
+ *
+ * @param publicId The entity's public identifier, or null.
+ * @param systemId The entity's system identifier.
+ * @return A new InputSource or null for the default.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ * @exception java.io.IOException The client may throw an
+ * I/O-related exception while obtaining the
+ * new InputSource.
+ */
+ public InputSource resolveEntity (String publicId, String systemId)
+ throws SAXException, IOException
+ {
+ if (entityResolver != null) {
+ return entityResolver.resolveEntity(publicId, systemId);
+ } else {
+ return null;
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.DTDHandler.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Filter a notation declaration event.
+ *
+ * @param name The notation name.
+ * @param publicId The notation's public identifier, or null.
+ * @param systemId The notation's system identifier, or null.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void notationDecl (String name, String publicId, String systemId)
+ throws SAXException
+ {
+ if (dtdHandler != null) {
+ dtdHandler.notationDecl(name, publicId, systemId);
+ }
+ }
+
+
+ /**
+ * Filter an unparsed entity declaration event.
+ *
+ * @param name The entity name.
+ * @param publicId The entity's public identifier, or null.
+ * @param systemId The entity's system identifier, or null.
+ * @param notationName The name of the associated notation.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void unparsedEntityDecl (String name, String publicId,
+ String systemId, String notationName)
+ throws SAXException
+ {
+ if (dtdHandler != null) {
+ dtdHandler.unparsedEntityDecl(name, publicId, systemId,
+ notationName);
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.ContentHandler.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Filter a new document locator event.
+ *
+ * @param locator The document locator.
+ */
+ public void setDocumentLocator (Locator locator)
+ {
+ this.locator = locator;
+ if (contentHandler != null) {
+ contentHandler.setDocumentLocator(locator);
+ }
+ }
+
+
+ /**
+ * Filter a start document event.
+ *
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void startDocument ()
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.startDocument();
+ }
+ }
+
+
+ /**
+ * Filter an end document event.
+ *
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void endDocument ()
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.endDocument();
+ }
+ }
+
+
+ /**
+ * Filter a start Namespace prefix mapping event.
+ *
+ * @param prefix The Namespace prefix.
+ * @param uri The Namespace URI.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void startPrefixMapping (String prefix, String uri)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.startPrefixMapping(prefix, uri);
+ }
+ }
+
+
+ /**
+ * Filter an end Namespace prefix mapping event.
+ *
+ * @param prefix The Namespace prefix.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void endPrefixMapping (String prefix)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.endPrefixMapping(prefix);
+ }
+ }
+
+
+ /**
+ * Filter a start element event.
+ *
+ * @param uri The element's Namespace URI, or the empty string.
+ * @param localName The element's local name, or the empty string.
+ * @param qName The element's qualified (prefixed) name, or the empty
+ * string.
+ * @param atts The element's attributes.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void startElement (String uri, String localName, String qName,
+ Attributes atts)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.startElement(uri, localName, qName, atts);
+ }
+ }
+
+
+ /**
+ * Filter an end element event.
+ *
+ * @param uri The element's Namespace URI, or the empty string.
+ * @param localName The element's local name, or the empty string.
+ * @param qName The element's qualified (prefixed) name, or the empty
+ * string.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void endElement (String uri, String localName, String qName)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.endElement(uri, localName, qName);
+ }
+ }
+
+
+ /**
+ * Filter a character data event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use from the array.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.characters(ch, start, length);
+ }
+ }
+
+
+ /**
+ * Filter an ignorable whitespace event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use from the array.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.ignorableWhitespace(ch, start, length);
+ }
+ }
+
+
+ /**
+ * Filter a processing instruction event.
+ *
+ * @param target The processing instruction target.
+ * @param data The text following the target.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.processingInstruction(target, data);
+ }
+ }
+
+
+ /**
+ * Filter a skipped entity event.
+ *
+ * @param name The name of the skipped entity.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void skippedEntity (String name)
+ throws SAXException
+ {
+ if (contentHandler != null) {
+ contentHandler.skippedEntity(name);
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.ErrorHandler.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Filter a warning event.
+ *
+ * @param e The warning as an exception.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void warning (SAXParseException e)
+ throws SAXException
+ {
+ if (errorHandler != null) {
+ errorHandler.warning(e);
+ }
+ }
+
+
+ /**
+ * Filter an error event.
+ *
+ * @param e The error as an exception.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void error (SAXParseException e)
+ throws SAXException
+ {
+ if (errorHandler != null) {
+ errorHandler.error(e);
+ }
+ }
+
+
+ /**
+ * Filter a fatal error event.
+ *
+ * @param e The error as an exception.
+ * @exception org.xml.sax.SAXException The client may throw
+ * an exception during processing.
+ */
+ public void fatalError (SAXParseException e)
+ throws SAXException
+ {
+ if (errorHandler != null) {
+ errorHandler.fatalError(e);
+ }
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal methods.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set up before a parse.
+ *
+ * <p>Before every parse, check whether the parent is
+ * non-null, and re-register the filter for all of the
+ * events.</p>
+ */
+ private void setupParse ()
+ {
+ if (parent == null) {
+ throw new NullPointerException("No parent for filter");
+ }
+ parent.setEntityResolver(this);
+ parent.setDTDHandler(this);
+ parent.setContentHandler(this);
+ parent.setErrorHandler(this);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ private XMLReader parent = null;
+ private Locator locator = null;
+ private EntityResolver entityResolver = null;
+ private DTDHandler dtdHandler = null;
+ private ContentHandler contentHandler = null;
+ private ErrorHandler errorHandler = null;
+
+}
+
+// end of XMLFilterImpl.java
diff --git a/src/org/xml/sax/helpers/XMLReaderAdapter.java b/src/org/xml/sax/helpers/XMLReaderAdapter.java
new file mode 100644
index 0000000..0c006a5
--- /dev/null
+++ b/src/org/xml/sax/helpers/XMLReaderAdapter.java
@@ -0,0 +1,538 @@
+// XMLReaderAdapter.java - adapt an SAX2 XMLReader to a SAX1 Parser
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: XMLReaderAdapter.java,v 1.9 2004/04/26 17:34:35 dmegginson Exp $
+
+package org.xml.sax.helpers;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import org.xml.sax.Parser; // deprecated
+import org.xml.sax.Locator;
+import org.xml.sax.InputSource;
+import org.xml.sax.AttributeList; // deprecated
+import org.xml.sax.EntityResolver;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.DocumentHandler; // deprecated
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXNotSupportedException;
+
+
+/**
+ * Adapt a SAX2 XMLReader as a SAX1 Parser.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class wraps a SAX2 {@link org.xml.sax.XMLReader XMLReader}
+ * and makes it act as a SAX1 {@link org.xml.sax.Parser Parser}. The XMLReader
+ * must support a true value for the
+ * http://xml.org/sax/features/namespace-prefixes property or parsing will fail
+ * with a {@link org.xml.sax.SAXException SAXException}; if the XMLReader
+ * supports a false value for the http://xml.org/sax/features/namespaces
+ * property, that will also be used to improve efficiency.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.Parser
+ * @see org.xml.sax.XMLReader
+ */
+public class XMLReaderAdapter implements Parser, ContentHandler
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Constructor.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Create a new adapter.
+ *
+ * <p>Use the "org.xml.sax.driver" property to locate the SAX2
+ * driver to embed.</p>
+ *
+ * @exception org.xml.sax.SAXException If the embedded driver
+ * cannot be instantiated or if the
+ * org.xml.sax.driver property is not specified.
+ */
+ public XMLReaderAdapter ()
+ throws SAXException
+ {
+ setup(XMLReaderFactory.createXMLReader());
+ }
+
+
+ /**
+ * Create a new adapter.
+ *
+ * <p>Create a new adapter, wrapped around a SAX2 XMLReader.
+ * The adapter will make the XMLReader act like a SAX1
+ * Parser.</p>
+ *
+ * @param xmlReader The SAX2 XMLReader to wrap.
+ * @exception java.lang.NullPointerException If the argument is null.
+ */
+ public XMLReaderAdapter (XMLReader xmlReader)
+ {
+ setup(xmlReader);
+ }
+
+
+
+ /**
+ * Internal setup.
+ *
+ * @param xmlReader The embedded XMLReader.
+ */
+ private void setup (XMLReader xmlReader)
+ {
+ if (xmlReader == null) {
+ throw new NullPointerException("XMLReader must not be null");
+ }
+ this.xmlReader = xmlReader;
+ qAtts = new AttributesAdapter();
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.Parser.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set the locale for error reporting.
+ *
+ * <p>This is not supported in SAX2, and will always throw
+ * an exception.</p>
+ *
+ * @param locale the locale for error reporting.
+ * @see org.xml.sax.Parser#setLocale
+ * @exception org.xml.sax.SAXException Thrown unless overridden.
+ */
+ public void setLocale (Locale locale)
+ throws SAXException
+ {
+ throw new SAXNotSupportedException("setLocale not supported");
+ }
+
+
+ /**
+ * Register the entity resolver.
+ *
+ * @param resolver The new resolver.
+ * @see org.xml.sax.Parser#setEntityResolver
+ */
+ public void setEntityResolver (EntityResolver resolver)
+ {
+ xmlReader.setEntityResolver(resolver);
+ }
+
+
+ /**
+ * Register the DTD event handler.
+ *
+ * @param handler The new DTD event handler.
+ * @see org.xml.sax.Parser#setDTDHandler
+ */
+ public void setDTDHandler (DTDHandler handler)
+ {
+ xmlReader.setDTDHandler(handler);
+ }
+
+
+ /**
+ * Register the SAX1 document event handler.
+ *
+ * <p>Note that the SAX1 document handler has no Namespace
+ * support.</p>
+ *
+ * @param handler The new SAX1 document event handler.
+ * @see org.xml.sax.Parser#setDocumentHandler
+ */
+ public void setDocumentHandler (DocumentHandler handler)
+ {
+ documentHandler = handler;
+ }
+
+
+ /**
+ * Register the error event handler.
+ *
+ * @param handler The new error event handler.
+ * @see org.xml.sax.Parser#setErrorHandler
+ */
+ public void setErrorHandler (ErrorHandler handler)
+ {
+ xmlReader.setErrorHandler(handler);
+ }
+
+
+ /**
+ * Parse the document.
+ *
+ * <p>This method will throw an exception if the embedded
+ * XMLReader does not support the
+ * http://xml.org/sax/features/namespace-prefixes property.</p>
+ *
+ * @param systemId The absolute URL of the document.
+ * @exception java.io.IOException If there is a problem reading
+ * the raw content of the document.
+ * @exception org.xml.sax.SAXException If there is a problem
+ * processing the document.
+ * @see #parse(org.xml.sax.InputSource)
+ * @see org.xml.sax.Parser#parse(java.lang.String)
+ */
+ public void parse (String systemId)
+ throws IOException, SAXException
+ {
+ parse(new InputSource(systemId));
+ }
+
+
+ /**
+ * Parse the document.
+ *
+ * <p>This method will throw an exception if the embedded
+ * XMLReader does not support the
+ * http://xml.org/sax/features/namespace-prefixes property.</p>
+ *
+ * @param input An input source for the document.
+ * @exception java.io.IOException If there is a problem reading
+ * the raw content of the document.
+ * @exception org.xml.sax.SAXException If there is a problem
+ * processing the document.
+ * @see #parse(java.lang.String)
+ * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
+ */
+ public void parse (InputSource input)
+ throws IOException, SAXException
+ {
+ setupXMLReader();
+ xmlReader.parse(input);
+ }
+
+
+ /**
+ * Set up the XML reader.
+ */
+ private void setupXMLReader ()
+ throws SAXException
+ {
+ xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+ try {
+ xmlReader.setFeature("http://xml.org/sax/features/namespaces",
+ false);
+ } catch (SAXException e) {
+ // NO OP: it's just extra information, and we can ignore it
+ }
+ xmlReader.setContentHandler(this);
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Implementation of org.xml.sax.ContentHandler.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Set a document locator.
+ *
+ * @param locator The document locator.
+ * @see org.xml.sax.ContentHandler#setDocumentLocator
+ */
+ public void setDocumentLocator (Locator locator)
+ {
+ if (documentHandler != null)
+ documentHandler.setDocumentLocator(locator);
+ }
+
+
+ /**
+ * Start document event.
+ *
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#startDocument
+ */
+ public void startDocument ()
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.startDocument();
+ }
+
+
+ /**
+ * End document event.
+ *
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#endDocument
+ */
+ public void endDocument ()
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.endDocument();
+ }
+
+
+ /**
+ * Adapt a SAX2 start prefix mapping event.
+ *
+ * @param prefix The prefix being mapped.
+ * @param uri The Namespace URI being mapped to.
+ * @see org.xml.sax.ContentHandler#startPrefixMapping
+ */
+ public void startPrefixMapping (String prefix, String uri)
+ {
+ }
+
+
+ /**
+ * Adapt a SAX2 end prefix mapping event.
+ *
+ * @param prefix The prefix being mapped.
+ * @see org.xml.sax.ContentHandler#endPrefixMapping
+ */
+ public void endPrefixMapping (String prefix)
+ {
+ }
+
+
+ /**
+ * Adapt a SAX2 start element event.
+ *
+ * @param uri The Namespace URI.
+ * @param localName The Namespace local name.
+ * @param qName The qualified (prefixed) name.
+ * @param atts The SAX2 attributes.
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#endDocument
+ */
+ public void startElement (String uri, String localName,
+ String qName, Attributes atts)
+ throws SAXException
+ {
+ if (documentHandler != null) {
+ qAtts.setAttributes(atts);
+ documentHandler.startElement(qName, qAtts);
+ }
+ }
+
+
+ /**
+ * Adapt a SAX2 end element event.
+ *
+ * @param uri The Namespace URI.
+ * @param localName The Namespace local name.
+ * @param qName The qualified (prefixed) name.
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#endElement
+ */
+ public void endElement (String uri, String localName,
+ String qName)
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.endElement(qName);
+ }
+
+
+ /**
+ * Adapt a SAX2 characters event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use.
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#characters
+ */
+ public void characters (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.characters(ch, start, length);
+ }
+
+
+ /**
+ * Adapt a SAX2 ignorable whitespace event.
+ *
+ * @param ch An array of characters.
+ * @param start The starting position in the array.
+ * @param length The number of characters to use.
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace
+ */
+ public void ignorableWhitespace (char ch[], int start, int length)
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.ignorableWhitespace(ch, start, length);
+ }
+
+
+ /**
+ * Adapt a SAX2 processing instruction event.
+ *
+ * @param target The processing instruction target.
+ * @param data The remainder of the processing instruction
+ * @exception org.xml.sax.SAXException The client may raise a
+ * processing exception.
+ * @see org.xml.sax.ContentHandler#processingInstruction
+ */
+ public void processingInstruction (String target, String data)
+ throws SAXException
+ {
+ if (documentHandler != null)
+ documentHandler.processingInstruction(target, data);
+ }
+
+
+ /**
+ * Adapt a SAX2 skipped entity event.
+ *
+ * @param name The name of the skipped entity.
+ * @see org.xml.sax.ContentHandler#skippedEntity
+ * @exception org.xml.sax.SAXException Throwable by subclasses.
+ */
+ public void skippedEntity (String name)
+ throws SAXException
+ {
+ }
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal state.
+ ////////////////////////////////////////////////////////////////////
+
+ XMLReader xmlReader;
+ DocumentHandler documentHandler;
+ AttributesAdapter qAtts;
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Internal class.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Internal class to wrap a SAX2 Attributes object for SAX1.
+ */
+ final class AttributesAdapter implements AttributeList
+ {
+ AttributesAdapter ()
+ {
+ }
+
+
+ /**
+ * Set the embedded Attributes object.
+ *
+ * @param The embedded SAX2 Attributes.
+ */
+ void setAttributes (Attributes attributes)
+ {
+ this.attributes = attributes;
+ }
+
+
+ /**
+ * Return the number of attributes.
+ *
+ * @return The length of the attribute list.
+ * @see org.xml.sax.AttributeList#getLength
+ */
+ public int getLength ()
+ {
+ return attributes.getLength();
+ }
+
+
+ /**
+ * Return the qualified (prefixed) name of an attribute by position.
+ *
+ * @return The qualified name.
+ * @see org.xml.sax.AttributeList#getName
+ */
+ public String getName (int i)
+ {
+ return attributes.getQName(i);
+ }
+
+
+ /**
+ * Return the type of an attribute by position.
+ *
+ * @return The type.
+ * @see org.xml.sax.AttributeList#getType(int)
+ */
+ public String getType (int i)
+ {
+ return attributes.getType(i);
+ }
+
+
+ /**
+ * Return the value of an attribute by position.
+ *
+ * @return The value.
+ * @see org.xml.sax.AttributeList#getValue(int)
+ */
+ public String getValue (int i)
+ {
+ return attributes.getValue(i);
+ }
+
+
+ /**
+ * Return the type of an attribute by qualified (prefixed) name.
+ *
+ * @return The type.
+ * @see org.xml.sax.AttributeList#getType(java.lang.String)
+ */
+ public String getType (String qName)
+ {
+ return attributes.getType(qName);
+ }
+
+
+ /**
+ * Return the value of an attribute by qualified (prefixed) name.
+ *
+ * @return The value.
+ * @see org.xml.sax.AttributeList#getValue(java.lang.String)
+ */
+ public String getValue (String qName)
+ {
+ return attributes.getValue(qName);
+ }
+
+ private Attributes attributes;
+ }
+
+}
+
+// end of XMLReaderAdapter.java
diff --git a/src/org/xml/sax/helpers/XMLReaderFactory.java b/src/org/xml/sax/helpers/XMLReaderFactory.java
new file mode 100644
index 0000000..f50be7a
--- /dev/null
+++ b/src/org/xml/sax/helpers/XMLReaderFactory.java
@@ -0,0 +1,202 @@
+// XMLReaderFactory.java - factory for creating a new reader.
+// http://www.saxproject.org
+// Written by David Megginson
+// and by David Brownell
+// NO WARRANTY! This class is in the Public Domain.
+// $Id: XMLReaderFactory.java,v 1.10 2002/04/22 01:00:13 dbrownell Exp $
+
+package org.xml.sax.helpers;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import org.xml.sax.XMLReader;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Factory for creating an XML reader.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class contains static methods for creating an XML reader
+ * from an explicit class name, or based on runtime defaults:</p>
+ *
+ * <pre>
+ * try {
+ * XMLReader myReader = XMLReaderFactory.createXMLReader();
+ * } catch (SAXException e) {
+ * System.err.println(e.getMessage());
+ * }
+ * </pre>
+ *
+ * <p><strong>Note to Distributions bundled with parsers:</strong>
+ * You should modify the implementation of the no-arguments
+ * <em>createXMLReader</em> to handle cases where the external
+ * configuration mechanisms aren't set up. That method should do its
+ * best to return a parser when one is in the class path, even when
+ * nothing bound its class name to <code>org.xml.sax.driver</code> so
+ * those configuration mechanisms would see it.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson, David Brownell
+ * @version 2.0.1 (sax2r2)
+ */
+final public class XMLReaderFactory
+{
+ /**
+ * Private constructor.
+ *
+ * <p>This constructor prevents the class from being instantiated.</p>
+ */
+ private XMLReaderFactory ()
+ {
+ }
+
+ private static final String property = "org.xml.sax.driver";
+
+ /**
+ * Attempt to create an XMLReader from system defaults.
+ * In environments which can support it, the name of the XMLReader
+ * class is determined by trying each these options in order, and
+ * using the first one which succeeds:</p> <ul>
+ *
+ * <li>If the system property <code>org.xml.sax.driver</code>
+ * has a value, that is used as an XMLReader class name. </li>
+ *
+ * <li>The JAR "Services API" is used to look for a class name
+ * in the <em>META-INF/services/org.xml.sax.driver</em> file in
+ * jarfiles available to the runtime.</li>
+ *
+ * <li> SAX parser distributions are strongly encouraged to provide
+ * a default XMLReader class name that will take effect only when
+ * previous options (on this list) are not successful.</li>
+ *
+ * <li>Finally, if {@link ParserFactory#makeParser()} can
+ * return a system default SAX1 parser, that parser is wrapped in
+ * a {@link ParserAdapter}. (This is a migration aid for SAX1
+ * environments, where the <code>org.xml.sax.parser</code> system
+ * property will often be usable.) </li>
+ *
+ * </ul>
+ *
+ * <p> In environments such as small embedded systems, which can not
+ * support that flexibility, other mechanisms to determine the default
+ * may be used. </p>
+ *
+ * <p>Note that many Java environments allow system properties to be
+ * initialized on a command line. This means that <em>in most cases</em>
+ * setting a good value for that property ensures that calls to this
+ * method will succeed, except when security policies intervene.
+ * This will also maximize application portability to older SAX
+ * environments, with less robust implementations of this method.
+ * </p>
+ *
+ * @return A new XMLReader.
+ * @exception org.xml.sax.SAXException If no default XMLReader class
+ * can be identified and instantiated.
+ * @see #createXMLReader(java.lang.String)
+ */
+ public static XMLReader createXMLReader ()
+ throws SAXException
+ {
+ String className = null;
+ ClassLoader loader = NewInstance.getClassLoader ();
+
+ // 1. try the JVM-instance-wide system property
+ try { className = System.getProperty (property); }
+ catch (RuntimeException e) { /* normally fails for applets */ }
+
+ // 2. if that fails, try META-INF/services/
+ if (className == null) {
+ try {
+ String service = "META-INF/services/" + property;
+ InputStream in;
+ BufferedReader reader;
+
+ if (loader == null)
+ in = ClassLoader.getSystemResourceAsStream (service);
+ else
+ in = loader.getResourceAsStream (service);
+
+ if (in != null) {
+ reader = new BufferedReader (
+ new InputStreamReader (in, "UTF8"));
+ className = reader.readLine ();
+ in.close ();
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ // 3. Distro-specific fallback
+ if (className == null) {
+// BEGIN DISTRIBUTION-SPECIFIC
+
+ // EXAMPLE:
+ // className = "com.example.sax.XmlReader";
+ // or a $JAVA_HOME/jre/lib/*properties setting...
+
+// END DISTRIBUTION-SPECIFIC
+ }
+
+ // do we know the XMLReader implementation class yet?
+ if (className != null)
+ return loadClass (loader, className);
+
+ // 4. panic -- adapt any SAX1 parser
+ try {
+ return new ParserAdapter (ParserFactory.makeParser ());
+ } catch (Exception e) {
+ throw new SAXException ("Can't create default XMLReader; "
+ + "is system property org.xml.sax.driver set?");
+ }
+ }
+
+
+ /**
+ * Attempt to create an XML reader from a class name.
+ *
+ * <p>Given a class name, this method attempts to load
+ * and instantiate the class as an XML reader.</p>
+ *
+ * <p>Note that this method will not be usable in environments where
+ * the caller (perhaps an applet) is not permitted to load classes
+ * dynamically.</p>
+ *
+ * @return A new XML reader.
+ * @exception org.xml.sax.SAXException If the class cannot be
+ * loaded, instantiated, and cast to XMLReader.
+ * @see #createXMLReader()
+ */
+ public static XMLReader createXMLReader (String className)
+ throws SAXException
+ {
+ return loadClass (NewInstance.getClassLoader (), className);
+ }
+
+ private static XMLReader loadClass (ClassLoader loader, String className)
+ throws SAXException
+ {
+ try {
+ return (XMLReader) NewInstance.newInstance (loader, className);
+ } catch (ClassNotFoundException e1) {
+ throw new SAXException("SAX2 driver class " + className +
+ " not found", e1);
+ } catch (IllegalAccessException e2) {
+ throw new SAXException("SAX2 driver class " + className +
+ " found but cannot be loaded", e2);
+ } catch (InstantiationException e3) {
+ throw new SAXException("SAX2 driver class " + className +
+ " loaded but cannot be instantiated (no empty public constructor?)",
+ e3);
+ } catch (ClassCastException e4) {
+ throw new SAXException("SAX2 driver class " + className +
+ " does not implement XMLReader", e4);
+ }
+ }
+}
diff --git a/src/org/xml/sax/helpers/package.html b/src/org/xml/sax/helpers/package.html
new file mode 100644
index 0000000..8f323c0
--- /dev/null
+++ b/src/org/xml/sax/helpers/package.html
@@ -0,0 +1,11 @@
+<HTML><HEAD>
+<!-- $Id: package.html,v 1.6 2002/01/30 20:52:39 dbrownell Exp $ -->
+</HEAD><BODY>
+
+<p>This package contains "helper" classes, including
+support for bootstrapping SAX-based applications.
+
+<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+for more information about SAX.</p>
+
+</BODY></HTML>
diff --git a/src/org/xml/sax/package.html b/src/org/xml/sax/package.html
new file mode 100644
index 0000000..dd7030e
--- /dev/null
+++ b/src/org/xml/sax/package.html
@@ -0,0 +1,297 @@
+<html><head>
+<!-- $Id: package.html,v 1.18 2004/04/21 13:06:01 dmegginson Exp $ -->
+</head><body>
+
+<p> This package provides the core SAX APIs.
+Some SAX1 APIs are deprecated to encourage integration of
+namespace-awareness into designs of new applications
+and into maintenance of existing infrastructure. </p>
+
+<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+for more information about SAX.</p>
+
+
+<h2> SAX2 Standard Feature Flags </h2>
+
+<p> One of the essential characteristics of SAX2 is that it added
+feature flags which can be used to examine and perhaps modify
+parser modes, in particular modes such as validation.
+Since features are identified by (absolute) URIs, anyone
+can define such features.
+Currently defined standard feature URIs have the prefix
+<code>http://xml.org/sax/features/</code> before an identifier such as
+<code>validation</code>. Turn features on or off using
+<em>setFeature</em>. Those standard identifiers are: </p>
+
+
+<table border="1" cellpadding="3" cellspacing="0" width="100%">
+ <tr align="center" bgcolor="#ccccff">
+ <th>Feature ID</th>
+ <th>Access</th>
+ <th>Default</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>external-general-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Reports whether this parser processes external
+ general entities; always true if validating.
+ </td>
+ </tr>
+
+ <tr>
+ <td>external-parameter-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Reports whether this parser processes external
+ parameter entities; always true if validating.
+ </td>
+ </tr>
+
+ <tr>
+ <td>is-standalone</td>
+ <td>(parsing) <em>read-only</em>, (not parsing) <em>none</em></td>
+ <td>not applicable</td>
+ <td> May be examined only during a parse, after the
+ <em>startDocument()</em> callback has been completed; read-only.
+ The value is true if the document specified standalone="yes" in
+ its XML declaration, and otherwise is false.
+ </td>
+ </tr>
+
+ <tr>
+ <td>lexical-handler/parameter-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> A value of "true" indicates that the LexicalHandler will report
+ the beginning and end of parameter entities.
+ </td>
+ </tr>
+
+ <tr>
+ <td>namespaces</td>
+ <td><em>read/write</em></td>
+ <td>true</td>
+ <td> A value of "true" indicates namespace URIs and unprefixed local names
+ for element and attribute names will be available.
+ </td>
+ </tr>
+
+ <tr>
+ <td>namespace-prefixes</td>
+ <td><em>read/write</em></td>
+ <td>false</td>
+ <td> A value of "true" indicates that XML qualified names (with prefixes) and
+ attributes (including <em>xmlns*</em> attributes) will be available.
+ </td>
+ </tr>
+
+ <tr>
+ <td>resolve-dtd-uris</td>
+ <td><em>read/write</em></td>
+ <td><em>true</em></td>
+ <td> A value of "true" indicates that system IDs in declarations will
+ be absolutized (relative to their base URIs) before reporting.
+ (That is the default behavior for all SAX2 XML parsers.)
+ A value of "false" indicates those IDs will not be absolutized;
+ parsers will provide the base URI from
+ <em>Locator.getSystemId()</em>.
+ This applies to system IDs passed in <ul>
+ <li><em>DTDHandler.notationDecl()</em>,
+ <li><em>DTDHandler.unparsedEntityDecl()</em>, and
+ <li><em>DeclHandler.externalEntityDecl()</em>.
+ </ul>
+ It does not apply to <em>EntityResolver.resolveEntity()</em>,
+ which is not used to report declarations, or to
+ <em>LexicalHandler.startDTD()</em>, which already provides
+ the non-absolutized URI.
+ </td>
+ </tr>
+
+ <tr>
+ <td>string-interning</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Has a value of "true" if all XML names (for elements, prefixes,
+ attributes, entities, notations, and local names),
+ as well as Namespace URIs, will have been interned
+ using <em>java.lang.String.intern</em>. This supports fast
+ testing of equality/inequality against string constants,
+ rather than forcing slower calls to <em>String.equals()</em>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>unicode-normalization-checking</td>
+ <td><em>read/write</em></td>
+ <td><em>false</em></td>
+ <td> Controls whether the parser reports Unicode normalization
+ errors as described in section 2.13 and Appendix B of the
+ XML 1.1 Recommendation. If true, Unicode normalization
+ errors are reported using the ErrorHandler.error() callback.
+ Such errors are not fatal in themselves (though, obviously,
+ other Unicode-related encoding errors may be).
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-attributes2</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the <em>Attributes</em> objects passed by
+ this parser in <em>ContentHandler.startElement()</em>
+ implement the <a href="ext/Attributes2.html"
+ ><em>org.xml.sax.ext.Attributes2</em></a> interface.
+ That interface exposes additional DTD-related information,
+ such as whether the attribute was specified in the
+ source text rather than defaulted.
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-locator2</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the <em>Locator</em> objects passed by
+ this parser in <em>ContentHandler.setDocumentLocator()</em>
+ implement the <a href="ext/Locator2.html"
+ ><em>org.xml.sax.ext.Locator2</em></a> interface.
+ That interface exposes additional entity information,
+ such as the character encoding and XML version used.
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-entity-resolver2</td>
+ <td><em>read/write</em></td>
+ <td><em>true</em></td>
+ <td> Returns "true" if, when <em>setEntityResolver</em> is given
+ an object implementing the <a href="ext/EntityResolver2.html"
+ ><em>org.xml.sax.ext.EntityResolver2</em></a> interface,
+ those new methods will be used.
+ Returns "false" to indicate that those methods will not be used.
+ </td>
+ </tr>
+
+ <tr>
+ <td>validation</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Controls whether the parser is reporting all validity
+ errors; if true, all external entities will be read.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xmlns-uris</td>
+ <td><em>read/write</em></td>
+ <td><em>false</em></td>
+ <td> Controls whether, when the <em>namespace-prefixes</em> feature
+ is set, the parser treats namespace declaration attributes as
+ being in the <em>http://www.w3.org/2000/xmlns/</em> namespace.
+ By default, SAX2 conforms to the original "Namespaces in XML"
+ Recommendation, which explicitly states that such attributes are
+ not in any namespace.
+ Setting this optional flag to "true" makes the SAX2 events conform to
+ a later backwards-incompatible revision of that recommendation,
+ placing those attributes in a namespace.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xml-1.1</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the parser supports both XML 1.1 and XML 1.0.
+ Returns "false" if the parser supports only XML 1.0.
+ </td>
+ </tr>
+
+</table>
+
+<p> Support for the default values of the
+<em>namespaces</em> and <em>namespace-prefixes</em>
+properties is required.
+Support for any other feature flags is entirely optional.
+</p>
+
+<p> For default values not specified by SAX2,
+each XMLReader implementation specifies its default,
+or may choose not to expose the feature flag.
+Unless otherwise specified here,
+implementations may support changing current values
+of these standard feature flags, but not while parsing.
+</p>
+
+<h2> SAX2 Standard Handler and Property IDs </h2>
+
+<p> For parser interface characteristics that are described
+as objects, a separate namespace is defined. The
+objects in this namespace are again identified by URI, and
+the standard property URIs have the prefix
+<code>http://xml.org/sax/properties/</code> before an identifier such as
+<code>lexical-handler</code> or
+<code>dom-node</code>. Manage those properties using
+<em>setProperty()</em>. Those identifiers are: </p>
+
+<table border="1" cellpadding="3" cellspacing="0" width="100%">
+ <tr align="center" bgcolor="#ccccff">
+ <th>Property ID</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>declaration-handler</td>
+ <td> Used to see most DTD declarations except those treated
+ as lexical ("document element name is ...") or which are
+ mandatory for all SAX parsers (<em>DTDHandler</em>).
+ The Object must implement <a href="ext/DeclHandler.html"
+ ><em>org.xml.sax.ext.DeclHandler</em></a>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>document-xml-version</td>
+ <td> May be examined only during a parse, after the startDocument()
+ callback has been completed; read-only. This property is a
+ literal string describing the actual XML version of the document,
+ such as "1.0" or "1.1".
+ </td>
+ </tr>
+
+ <tr>
+ <td>dom-node</td>
+ <td> For "DOM Walker" style parsers, which ignore their
+ <em>parser.parse()</em> parameters, this is used to
+ specify the DOM (sub)tree being walked by the parser.
+ The Object must implement the
+ <em>org.w3c.dom.Node</em> interface.
+ </td>
+ </tr>
+
+ <tr>
+ <td>lexical-handler</td>
+ <td> Used to see some syntax events that are essential in some
+ applications: comments, CDATA delimiters, selected general
+ entity inclusions, and the start and end of the DTD
+ (and declaration of document element name).
+ The Object must implement <a href="ext/LexicalHandler.html"
+ ><em>org.xml.sax.ext.LexicalHandler</em></a>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xml-string</td>
+ <td> Readable only during a parser callback, this exposes a <b>TBS</b>
+ chunk of characters responsible for the current event. </td>
+ </tr>
+
+</table>
+
+<p> All of these standard properties are optional;
+XMLReader implementations need not support them.
+</p>
+
+</body></html>
\ No newline at end of file
diff --git a/src/phyutility/concat/Concat.java b/src/phyutility/concat/Concat.java
new file mode 100644
index 0000000..98d57db
--- /dev/null
+++ b/src/phyutility/concat/Concat.java
@@ -0,0 +1,278 @@
+package phyutility.concat;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import jebl.evolution.io.*;
+import jebl.evolution.sequences.*;
+import jebl.evolution.taxa.Taxon;
+
+public class Concat {
+ private ArrayList <String> files;
+ private ArrayList <File> refiles;
+ private ArrayList <String> finalNames;
+ private ArrayList <ArrayList<Sequence>> allSeqs;
+ private ArrayList <Sequence> finalSeqs;
+ private ArrayList <Integer> geneLengths;
+ private String seqtype;
+
+
+ public Concat(ArrayList <String> files, String seqtype){
+ this.files = files;
+ this.seqtype = seqtype;
+ finalNames = new ArrayList <String>();
+ geneLengths = new ArrayList<Integer>();
+ allSeqs = new ArrayList <ArrayList<Sequence>>();
+ finalSeqs = new ArrayList <Sequence> ();
+ refiles = new ArrayList<File>();
+ run();
+ }
+
+ private void run(){
+ /*
+ * this should union the taxa names
+ * and make sure that genelengths are correct
+ */
+ SequenceType usetype = null;
+ if (seqtype.compareTo("test") == 0)
+ usetype = testForSeqType(files.get(0));
+ else if(seqtype.compareTo("nucleotide") == 0)
+ usetype = SequenceType.NUCLEOTIDE;
+ else if(seqtype.compareTo("aa") == 0)
+ usetype = SequenceType.AMINO_ACID;
+ for(int i=0;i<files.size();i++){
+ String filename = (String)files.get(i);
+ File file = new File(filename);
+ refiles.add(file);
+ if(testForNexus(filename) == false){
+ try {
+ FastaImporter fi = new FastaImporter(file,usetype);
+ ArrayList<Sequence> seqs = (ArrayList<Sequence>)fi.importSequences();
+ if(testDups(seqs) == true){
+ System.err.println("you have duplicate taxa in file "+filename);
+ System.exit(0);
+ }
+ allSeqs.add(seqs);
+ union(seqs);
+ if(testSeqLengths(seqs)==true){
+ geneLengths.add(seqs.get(0).getLength());
+ }else{
+ System.err.println("you have different lengths in file number "+(i+1));
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ try {
+ FileReader fr = new FileReader(file);
+ NexusImporter fi = new NexusImporter(fr);
+ ArrayList<Sequence> seqs = (ArrayList<Sequence>)fi.importSequences();
+ allSeqs.add(seqs);
+ union(seqs);
+ if(testSeqLengths(seqs)==true){
+ geneLengths.add(seqs.get(0).getLength());
+ }else{
+ System.err.println("you have different lengths in file number "+(i+1));
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ /*
+ * this creates a new sequence (the final one) from the union which will be used to make the concatenated sequences
+ */
+ for(int i=0;i<finalNames.size();i++){
+ BasicSequence seq = new BasicSequence(usetype,Taxon.getTaxon(finalNames.get(i)),"");
+ finalSeqs.add(seq);
+ }
+ /*
+ * this should make the strings for each taxa that will be used
+ * for nexus file
+ */
+ for(int i=0;i<allSeqs.size();i++){
+ for(int j=0;j<finalSeqs.size();j++){
+ boolean here = false;
+ for(int k=0;k<allSeqs.get(i).size();k++){
+ if(finalSeqs.get(j).getTaxon().getName().compareTo(allSeqs.get(i).get(k).getTaxon().getName())==0){
+ finalSeqs.set(j,new BasicSequence(usetype,finalSeqs.get(j).getTaxon(),
+ (finalSeqs.get(j).getString()+allSeqs.get(i).get(k).getString())));
+ here = true;
+ }
+ }
+ if(here == false){
+ String tst = "";
+ for(int k = 0;k<geneLengths.get(i);k++){
+ tst = tst+"-";
+ }
+ finalSeqs.set(j,new BasicSequence(usetype,finalSeqs.get(j).getTaxon(),
+ (finalSeqs.get(j).getString()+tst)));
+ }
+ }
+ }
+ }
+
+
+
+ private void union(ArrayList<Sequence> seqs){
+ if(finalNames.size()<1){
+ for(int i=0;i< seqs.size();i++){
+ finalNames.add(((Sequence)seqs.get(i)).getTaxon().getName().trim());
+ }
+ }else{
+ for(int i=0;i<seqs.size();i++){
+ boolean b = false;
+ for(int j = 0;j<finalNames.size();j++){
+ if(((Sequence)seqs.get(i)).getTaxon().getName().trim().compareTo(finalNames.get(j))==0){
+ b = true;
+ }
+ }
+ if(b == false){
+ finalNames.add(((Sequence)seqs.get(i)).getTaxon().getName().trim());
+ }
+ }
+ }
+ }
+
+ private boolean testSeqLengths(ArrayList<Sequence> seqs){
+ boolean ret = true;
+ int j = seqs.get(0).getLength();
+ for(int i=1;i<seqs.size();i++){
+ if(seqs.get(i).getLength()!=j)
+ ret = false;
+ }
+ return ret;
+ }
+
+ private boolean testDups(ArrayList<Sequence> seqs){
+ ArrayList<String> names = new ArrayList<String>();
+ for(int i=0;i<seqs.size();i++){
+ boolean b = false;
+ for(int j = 0;j<names.size();j++){
+ if(((Sequence)seqs.get(i)).getTaxon().getName().trim().compareTo(names.get(j))==0){
+ b = true;
+ }
+ }
+ if(b == false){
+ names.add(((Sequence)seqs.get(i)).getTaxon().getName().trim());
+ }else{
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public void printtofileNEXUS(String outfile){
+ try {
+ FileWriter fw = new FileWriter(outfile, false);
+ fw.write("#NEXUS\n");
+ fw.write("BEGIN DATA;\n");
+ fw.write("\t[");
+ int cur = 1;
+ for(int i=0;i<geneLengths.size();i++){
+ fw.write(refiles.get(i).getName()+"_gene"+(i+1)+" "+cur+"-"+(cur+geneLengths.get(i)-1)+" ");
+ cur += geneLengths.get(i);
+ }fw.write("]\n");
+ fw.write("\tDIMENSIONS NTAX="+finalSeqs.size()+" NCHAR="+finalSeqs.get(0).getLength()+";\n");
+ fw.write("\tFORMAT DATATYPE = DNA GAP = - MISSING = ?;\n");
+ fw.write("\tMATRIX\n");
+ for(int i=0;i<finalSeqs.size();i++){
+ fw.write("\t"+finalSeqs.get(i).getTaxon().getName().replaceAll(" ", "_")+"\t"+finalSeqs.get(i).getString()+"\n");
+ }
+ fw.write(";\n");
+ fw.write("END;\n\n");
+ fw.flush();
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public void printtoscreenNEXUS(){
+ System.out.print("#NEXUS\n");
+ System.out.print("BEGIN DATA;\n");
+ System.out.print("\t[");
+ int cur = 1;
+ for(int i=0;i<geneLengths.size();i++){
+ System.out.print(refiles.get(i).getName()+"_gene"+(i+1)+" "+cur+"-"+(cur+geneLengths.get(i)-1)+" ");
+ cur += geneLengths.get(i);
+ }System.out.print("]\n");
+ System.out.print("\tDIMENSIONS NTAX="+finalSeqs.size()+" NCHAR="+finalSeqs.get(0).getLength()+";\n");
+ System.out.print("\tFORMAT DATATYPE = DNA GAP = - MISSING = ?;\n");
+ System.out.print("\tMATRIX\n");
+ for(int i=0;i<finalSeqs.size();i++){
+ System.out.print("\t"+finalSeqs.get(i).getTaxon().getName().replaceAll(" ","_")+"\t"+finalSeqs.get(i).getString()+"\n");
+ }
+ System.out.print(";\n");
+ System.out.print("END;\n\n");
+ }
+
+ public void printtofileFASTA(String outfile){
+ try {
+ FileWriter fw = new FileWriter(outfile, false);
+ fw.write("#NEXUS\n");
+ fw.write("BEGIN DATA;\n");
+ fw.write("\t[");
+ int cur = 1;
+ for(int i=0;i<geneLengths.size();i++){
+ fw.write("gene"+(i+1)+" "+cur+"-"+(cur+geneLengths.get(i)-1)+" ");
+ cur += geneLengths.get(i);
+ }fw.write("]\n");
+ fw.write("\tDIMENSIONS NTAX="+finalSeqs.size()+" NCHAR="+finalSeqs.get(0).getLength()+";\n");
+ fw.write("\tFORMAT DATATYPE = DNA GAP = - MISSING = ?;\n");
+ fw.write("\tMATRIX\n");
+ for(int i=0;i<finalSeqs.size();i++){
+ fw.write("\t"+finalSeqs.get(i).getTaxon().getName()+"\t"+finalSeqs.get(i).getString()+"\n");
+ }
+ fw.write(";\n");
+ fw.write("END;\n\n");
+ fw.flush();
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private boolean testForNexus(String filename){
+ boolean ret = false;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ ret = true;
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+
+ private SequenceType testForSeqType(String filename){
+ SequenceType ret = SequenceType.NUCLEOTIDE;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ while(str.length()<5 || str.startsWith(">")==true)
+ str = br.readLine();
+ if(str.length()>5 && str.startsWith(">") ==false)
+ ret = jebl.evolution.sequences.Utils.guessSequenceType(str);
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+}
diff --git a/src/phyutility/consensus/Consensus.java b/src/phyutility/consensus/Consensus.java
new file mode 100644
index 0000000..29b6be7
--- /dev/null
+++ b/src/phyutility/consensus/Consensus.java
@@ -0,0 +1,41 @@
+package phyutility.consensus;
+import jebl.evolution.trees.*;
+import jebl.evolution.io.*;
+
+import java.io.*;
+import java.util.*;
+public class Consensus {
+ public Consensus(String filename, Double threshold, String outfile){
+ try {
+ //if(newick.toUpperCase() == "YES" || newick.toUpperCase() == "Y"){
+ NewickImporter ni = new NewickImporter(new FileReader(filename),true);
+ ArrayList<Tree> trees =(ArrayList<Tree>) ni.importTrees();
+ RootedTree [] tr = new RootedTree[trees.size()];
+ for(int i=0;i<tr.length;i++){
+ tr[i] = (RootedTree)trees.get(i);
+ }
+ GreedyRootedConsensusTreeBuilder tb = new GreedyRootedConsensusTreeBuilder(tr,threshold, "greedy", true);
+ FileWriter wr = new FileWriter(outfile);
+ NexusExporter ne = new NexusExporter(wr);
+ ne.exportTree(tb.build());
+ wr.close();
+ //}else{
+ // NexusImporter ni = new NexusImporter(new FileReader(filename));
+ // ArrayList<Tree> trees =(ArrayList<Tree>) ni.importTrees();
+ // RootedTree [] tr = new RootedTree[trees.size()];
+ // for(int i=0;i<tr.length;i++){
+ // tr[i] = (RootedTree)trees.get(i);
+ // }
+ // GreedyRootedConsensusTreeBuilder tb = new GreedyRootedConsensusTreeBuilder(tr,threshold, "greedy", true);
+ // FileWriter wr = new FileWriter(outfile);
+ // NewickExporter ne = new NewickExporter(wr);
+ // ne.exportTree(tb.build());
+ // wr.close();
+ //}
+ } catch (FileNotFoundException e) {} catch (IOException e) {} catch (ImportException e) {}
+ }
+ public static void main(String[] args) {
+ //infile thresh outfile new
+ Consensus m = new Consensus(args[0], Double.valueOf(args[1]), args[2]);
+ }
+}
diff --git a/src/phyutility/drb/WwdEmbedded.java b/src/phyutility/drb/WwdEmbedded.java
new file mode 100644
index 0000000..0c4cf34
--- /dev/null
+++ b/src/phyutility/drb/WwdEmbedded.java
@@ -0,0 +1,236 @@
+package phyutility.drb;
+
+import java.sql.*;
+
+public class WwdEmbedded {
+ // ## DEFINE VARIABLES SECTION ##
+ // define the driver to use
+// String driver = "org.apache.derby.jdbc.EmbeddedDriver";
+ String driver = "org.sqlite.JDBC";
+ // the database name
+ String dbName = "derbydatabase";
+ // define the Derby connection URL to use
+ String connectionURL = "jdbc:derby:" + dbName + ";create=true";
+
+ Connection conn = null;
+ Statement s;
+ PreparedStatement psInsert;
+ ResultSet myWishes;
+ String printLine = " __________________________________________________";
+ String createString = "CREATE TABLE TREES (TREE_ID INT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1) PRIMARY KEY, TREE_STRING VARCHAR(30000) NOT NULL) ";
+
+ String dropString = "DROP TABLE TREES";
+
+ public WwdEmbedded(String name) {
+ this.dbName = name;
+ connectionURL = "jdbc:derby:" + dbName + ";create=true";
+ // Beginning of JDBC code sections
+ // ## LOAD DRIVER SECTION ##
+ try {
+ /*
+ ** Load the Derby driver.
+ ** When the embedded Driver is used this action start the Derby engine.
+ ** Catch an error and suggest a CLASSPATH problem
+ */
+ Class.forName(driver);
+ System.out.println(driver + " loaded. ");
+ } catch (java.lang.ClassNotFoundException e) {
+ System.err.print("ClassNotFoundException: ");
+ System.err.println(e.getMessage());
+ System.out
+ .println("\n >>> Please check your CLASSPATH variable <<<\n");
+ }
+ //connectToDB();
+ //System.out.println("table made = "+makeTable(true));
+ //System.out.println(this.getTableTreeSize());
+ }
+
+ public int getTableTreeSize() {
+ int ret = 0;
+ try {
+ // Select all records in the WISH_LIST table
+ myWishes = s.executeQuery("select TREE_ID,TREE_STRING from TREES");
+ while (myWishes.next()) {
+ ret++;
+ }
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ return ret;
+ }
+
+ public void connectToDB() {
+ // Beginning of Primary DB access section
+ // ## BOOT DATABASE SECTION ##
+ try {
+ // Create (if needed) and connect to the database
+ conn = DriverManager.getConnection(connectionURL);
+ System.out.println("Connected to database " + dbName);
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ }
+
+ public boolean makeTable(boolean force) {
+ boolean make = false;
+ try {
+ // ## INITIAL SQL SECTION ##
+ // Create a statement to issue simple commands.
+ s = conn.createStatement();
+ // Call utility method to check if table exists.
+ // Create the table if needed
+ if (!WwdUtils.wwdChk4Table(conn) || force == true) {
+ System.out.println(" . . . . creating table TREES");
+ if (!WwdUtils.wwdChk4Table(conn)) {
+ s.execute(createString);
+ } else {
+ s.execute(dropString);
+ s.execute(createString);
+ }
+ make = true;
+ }
+ // Prepare the insert statement to use
+ psInsert = conn
+ .prepareStatement("insert into TREES(TREE_STRING) values (?)");
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ return make;
+ }
+
+ public void addTree(String tree) {
+ try {
+ //Insert the text entered into the WISH_ITEM table
+ psInsert.setString(1, tree);
+ psInsert.executeUpdate();
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ }
+
+ public String getTree(int num) {
+ String ret = "";
+ try {
+ // Select all records in the WISH_LIST table
+ myWishes = s
+ .executeQuery("select TREE_ID,TREE_STRING from TREES where TREE_ID = "
+ + num);
+ // Loop through the ResultSet and print the data
+ while (myWishes.next()) {
+ //System.out.println("TREE " + myWishes.getInt(1) + " = " + myWishes.getString(2));
+ ret = myWishes.getString(2);
+ }
+ // Close the resultSet
+ myWishes.close();
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ return ret;
+ }
+
+ public jade.tree.Tree getJadeTree(int num) {
+ String ret = "";
+ num = num+1;
+ try {
+ // Select all records in the WISH_LIST table
+ myWishes = s
+ .executeQuery("select TREE_ID,TREE_STRING from TREES where TREE_ID = "
+ + num);
+ // Loop through the ResultSet and print the data
+ while (myWishes.next()) {
+ //System.out.println("TREE " + myWishes.getInt(1) + " = " + myWishes.getString(2));
+ ret = myWishes.getString(2);
+ }
+ // Close the resultSet
+ myWishes.close();
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ jade.tree.Tree rettree = null;
+ //System.out.println(num+" "+ret);
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(ret);
+ rettree = tr.readTree();
+ return rettree;
+ }
+
+ public void closeDB() {
+ try {
+ // Release the resources (clean up )
+ psInsert.close();
+ s.close();
+ conn.close();
+ System.out.println("Closed connection");
+
+ // ## DATABASE SHUTDOWN SECTION ##
+ /*** In embedded mode, an application should shut down Derby.
+ Shutdown throws the XJ015 exception to confirm success. ***/
+ if (driver.equals("org.apache.derby.jdbc.EmbeddedDriver")) {
+ boolean gotSQLExc = false;
+ try {
+ DriverManager.getConnection("jdbc:derby:;shutdown=true");
+ } catch (SQLException se) {
+ if (se.getSQLState().equals("XJ015")) {
+ gotSQLExc = true;
+ }
+ }
+ if (!gotSQLExc) {
+ System.out.println("Database did not shut down normally");
+ } else {
+ System.out.println("Database shut down normally");
+ }
+ }
+
+ // Beginning of the primary catch block: uses errorPrint method
+ } catch (Throwable e) {
+ /* Catch all exceptions and pass them to
+ ** the exception reporting method */
+ System.out.println(" . . . exception thrown:");
+ errorPrint(e);
+ }
+ System.out.println("Working With Derby JDBC program ending.");
+ }
+
+ // ## DERBY EXCEPTION REPORTING CLASSES ##
+ /*** Exception reporting methods
+ ** with special handling of SQLExceptions
+ ***/
+ static void errorPrint(Throwable e) {
+ if (e instanceof SQLException)
+ SQLExceptionPrint((SQLException) e);
+ else {
+ System.out.println("A non SQL error occured.");
+ e.printStackTrace();
+ }
+ } // END errorPrint
+
+ // Iterates through a stack of SQLExceptions
+ static void SQLExceptionPrint(SQLException sqle) {
+ while (sqle != null) {
+ System.out.println("\n---SQLException Caught---\n");
+ System.out.println("SQLState: " + (sqle).getSQLState());
+ System.out.println("Severity: " + (sqle).getErrorCode());
+ System.out.println("Message: " + (sqle).getMessage());
+ sqle.printStackTrace();
+ sqle = sqle.getNextException();
+ }
+ } // END SQLExceptionPrint
+}
diff --git a/src/phyutility/drb/WwdUtils.java b/src/phyutility/drb/WwdUtils.java
new file mode 100644
index 0000000..a7f7b02
--- /dev/null
+++ b/src/phyutility/drb/WwdUtils.java
@@ -0,0 +1,54 @@
+package phyutility.drb;
+/*
+ Derby - WwdUtils.java - utilitity methods used by WwdEmbedded.java
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+ */
+
+import java.io.*;
+import java.sql.*;
+public class WwdUtils {
+
+
+ /*** Check for WISH_LIST table ****/
+ public static boolean wwdChk4Table (Connection conTst ) throws SQLException {
+ boolean chk = true;
+ boolean doCreate = false;
+ try {
+ Statement s = conTst.createStatement();
+ s.execute("SELECT * FROM TREES");
+ } catch (SQLException sqle) {
+ String theError = (sqle).getSQLState();
+ // System.out.println(" Utils GOT: " + theError);
+ /** If table exists will get - WARNING 02000: No row was found **/
+ if (theError.equals("42X05")) // Table does not exist
+ { return false;
+ } else if (theError.equals("42X14") || theError.equals("42821")) {
+ System.out.println("WwdChk4Table: Incorrect table definition. Drop table WISH_LIST and rerun this program");
+ throw sqle;
+ } else {
+ System.out.println("WwdChk4Table: Unhandled SQLException" );
+ throw sqle;
+ }
+ }
+ // System.out.println("Just got the warning - table exists OK ");
+ return true;
+ } /*** END wwdInitTable **/
+
+}
\ No newline at end of file
diff --git a/src/phyutility/jebl2jade/NexusToJade.java b/src/phyutility/jebl2jade/NexusToJade.java
new file mode 100644
index 0000000..71d36af
--- /dev/null
+++ b/src/phyutility/jebl2jade/NexusToJade.java
@@ -0,0 +1,73 @@
+package phyutility.jebl2jade;
+
+import jade.tree.TreeReader;
+
+import java.io.*;
+import java.util.*;
+
+import phyutility.drb.WwdEmbedded;
+
+import jebl.evolution.io.ImportException;
+
+public class NexusToJade {
+ public static ArrayList<jade.tree.Tree> getJadeFromJeblNexus(String filename){
+ ArrayList<jade.tree.Tree> ret = new ArrayList<jade.tree.Tree>();
+ try {
+ jebl.evolution.io.NexusImporter ni = new jebl.evolution.io.NexusImporter(new FileReader(filename));
+ List<jebl.evolution.trees.Tree> li = ni.importTrees();
+ for(int i=0;i<li.size();i++){
+ String st = jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)li.get(i));
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ br = new BufferedReader (new StringReader(st));
+ String str = "";
+ while((str = br.readLine())!=null){
+ tr.setTree(str+";");
+ ret.add(tr.readTree());
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
+ public static WwdEmbedded getJadeFromJeblNexusDerby(String filename){
+ WwdEmbedded ret = new WwdEmbedded("temp");
+ ret.connectToDB();
+ System.out.println("table made = "+ret.makeTable(true));
+ try {
+ jebl.evolution.io.NexusImporter ni = new jebl.evolution.io.NexusImporter(new FileReader(filename));
+ while(ni.hasTree()){
+ String st = jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)ni.importNextTree());
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ br = new BufferedReader (new StringReader(st));
+ String str = "";
+ while((str = br.readLine())!=null){
+ if(str.length()>1){
+ tr.setTree(str+";");
+ ret.addTree(tr.readTree().getRoot().getNewick(true)+";");
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return ret;
+ }
+}
diff --git a/src/phyutility/leafstability/Calculator.java b/src/phyutility/leafstability/Calculator.java
new file mode 100644
index 0000000..b4a7f3a
--- /dev/null
+++ b/src/phyutility/leafstability/Calculator.java
@@ -0,0 +1,23 @@
+package phyutility.leafstability;
+
+import jade.tree.*;
+import java.util.*;
+
+public class Calculator {
+ public Calculator(){}
+ public int calc(String [] inner, String [] outer, Tree tree){
+ int ret = 0;
+ Node inn = tree.getMRCA(inner);
+ Node out = tree.getMRCA(outer);
+ Node temp = inn;
+ ArrayList<Node> path = new ArrayList<Node>();
+ while(inn.getParent()!=null){
+ path.add(inn.getParent());
+ inn = inn.getParent();
+ }
+ if(path.contains((Node)out)){
+ ret ++;
+ }
+ return ret;
+ }
+}
diff --git a/src/phyutility/leafstability/Runner.java b/src/phyutility/leafstability/Runner.java
new file mode 100644
index 0000000..4ebf63c
--- /dev/null
+++ b/src/phyutility/leafstability/Runner.java
@@ -0,0 +1,113 @@
+package phyutility.leafstability;
+
+import java.util.*;
+import java.io.*;
+
+import jade.tree.*;
+
+public class Runner {
+
+ private String filename;
+ private ArrayList<Tree> trees = new ArrayList<Tree>();
+ private ArrayList<String> exnames = new ArrayList<String>();
+ private int [][] triplets;
+ private HashMap<int [],Double> results = new HashMap<int[],Double>();
+
+
+ public Runner(String filename){
+ this.filename = filename;
+ }
+
+ public Runner(ArrayList<Tree> trees){
+ this.trees = trees;
+ this.initializeRun();
+ }
+
+ public void readTrees(){
+ TreeReader tr = new TreeReader ();
+ BufferedReader br;
+ try {
+ br = new BufferedReader(new FileReader(filename));
+ String str = "";
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ trees.add(tr.readTree());
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ System.out.println("read "+trees.size()+" trees");
+ }
+
+ public void initializeRun(){
+ triplets = jade.math.NChooseM.iterate(trees.get(0).getExternalNodeCount(), 3);
+ System.out.println("number of triplets "+triplets.length);
+ for(int i=0;i<trees.get(0).getExternalNodeCount();i++){
+ exnames.add(trees.get(0).getExternalNode(i).getName());
+ }
+ }
+
+ public void run(){
+ Calculator cal = new Calculator();
+ for(int i=0;i<triplets.length;i++){
+ ArrayList<Integer> all = new ArrayList<Integer>();
+ if(i%100 == 0)
+ System.out.println("current triplet: "+i);
+ String [] inner = {exnames.get(triplets[i][0]),exnames.get(triplets[i][1])};
+ String [] outer = {exnames.get(triplets[i][0]),exnames.get(triplets[i][1]), exnames.get(triplets[i][2])};
+ int count = 0;
+ for(int j=0;j<trees.size();j++){
+ count = count + cal.calc(inner, outer, trees.get(j));
+ }
+ all.add(count);
+ inner[0] = exnames.get(triplets[i][0]);
+ inner[1] = exnames.get(triplets[i][2]);
+ count = 0;
+ for(int j=0;j<trees.size();j++){
+ count = count + cal.calc(inner, outer, trees.get(j));
+ }
+ all.add(count);
+ inner[0] = exnames.get(triplets[i][1]);
+ inner[1] = exnames.get(triplets[i][2]);
+ count = 0;
+ for(int j=0;j<trees.size();j++){
+ count = count + cal.calc(inner, outer, trees.get(j));
+ }
+ all.add(count);
+ Collections.sort(all);
+ results.put(triplets[i], (all.get(2)-all.get(1))/Double.valueOf(trees.size()));
+ }
+ }
+
+ public void printResults(){
+ for(int i=0;i<exnames.size();i++){
+ double mean = 0;
+ int count = 0;
+ for(int j=0;j<triplets.length;j++){
+ if(triplets[j][0] == i || triplets[j][1] == i || triplets[j][2] == i){
+ mean = mean + results.get(triplets[j]);
+ count ++;
+ }
+ }
+ mean = mean / count;
+ System.out.println(exnames.get(i)+"\t"+mean);
+ }
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+ Runner run = new Runner(args[0]);
+ run.readTrees();
+ run.initializeRun();
+ run.run();
+ run.printResults();
+ }
+
+}
diff --git a/src/phyutility/lineagemovement/Main.java b/src/phyutility/lineagemovement/Main.java
new file mode 100644
index 0000000..9dd8742
--- /dev/null
+++ b/src/phyutility/lineagemovement/Main.java
@@ -0,0 +1,200 @@
+package phyutility.lineagemovement;
+import java.io.*;
+import java.util.*;
+import jade.tree.*;
+
+public class Main {
+ private String filename;//out_use
+ private String tipname;
+ private ArrayList<String> tipnames;
+ private String mrcf;//0.5g
+ private ArrayList<Tree> trees = new ArrayList<Tree>();
+ private Tree mrct;
+ private HashMap<Node, Double> values = new HashMap<Node, Double>();
+ private boolean prune = false; //bad if node of interest is polytomy
+
+ public Main(String filename, String mrc, String tipname, String prune){
+ this.filename = filename;
+ this.tipname = tipname;
+ this.mrcf = mrc;
+ this.prune = true;
+ }
+
+ public Main(String filename, String mrc, String tipname){
+ this.filename = filename;
+ this.tipname = tipname;
+ this.mrcf = mrc;
+ this.prune = false;
+ }
+
+ //already read trees
+ public Main(ArrayList<Tree> trees, Tree mrc, ArrayList<String> tipnames){
+ this.trees = trees;
+ this.tipnames = tipnames;
+ this.mrct = mrc;
+ this.prune = false;
+ }
+
+
+ public void readTrees(){
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ try{
+ br = new BufferedReader (new FileReader(filename));
+ String str = "";
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ trees.add(tr.readTree());
+ }
+ }catch (FileNotFoundException e){}catch(IOException ioe){}
+ }
+
+ public Tree run(){
+ //readMRCTREE(); already read tree
+ if(prune == true){
+ for(int i=0;i<tipnames.size();i++){
+ mrct.pruneExternalNode(mrct.getExternalNode(tipnames.get(i)));
+ }
+ }
+ double x = 0;
+ for(int i=0;i<trees.size();i++){
+ Node parent = trees.get(i).getMRCA(tipnames).getParent();
+ for(int j=0;j<parent.getChildCount();j++){
+ if(parent.getChild(j)!=trees.get(i).getMRCA(tipnames) ){
+ parent = parent.getChild(j);
+ break;
+ }
+ }
+ getAllTipNodesFromInternalNode(parent);
+ for(int j=0;j<tempNs.size();j++){
+ //System.out.println("- "+tempNs.get(j).getName());
+ }
+ //trees.get(i).pruneExternalNode(trees.get(i).getExternalNode(tipname));
+ //parent = parent.getParent();
+ getAllTipNodesFromInternalNode(parent);
+ ArrayList<String> arr = new ArrayList<String>();
+ for(int j=0;j<tempNs.size();j++){
+ if(tempNs.get(j)!=trees.get(i).getMRCA(tipnames)){
+ arr.add(tempNs.get(j).getName());
+ //System.out.println("+ "+tempNs.get(j).getName());
+ }
+ }
+ Node n = mrct.getMRCA(arr);
+ if(values.get((Node)n) == null){
+ values.put((Node)n, 1.0);
+ x++;
+ }else{
+ values.put((Node)n, values.get((Node)n)+1.0);
+ x++;
+ }
+ }
+ Iterator it= values.keySet().iterator();
+ while(it.hasNext()){
+ Node a = ((Node)it.next());
+ a.setName(a.getName()+"_"+(values.get((Node)a)/x));
+ }
+ //System.out.println(mrct.getRoot().getNewick(false)+";");
+ return mrct;
+ }
+
+
+ public Tree oldrun(){
+ //readMRCTREE(); already read tree
+ if(prune == true){
+ for(int i=0;i<tipnames.size();i++){
+ mrct.pruneExternalNode(mrct.getExternalNode(tipnames.get(i)));
+ }
+ }
+ double x = 0;
+ for(int i=0;i<trees.size();i++){
+ Node parent = trees.get(i).getExternalNode(tipname).getParent();
+ for(int j=0;j<parent.getChildCount();j++){
+ if(parent.getChild(j)!=trees.get(i).getExternalNode(tipname) ){
+ parent = parent.getChild(j);
+ break;
+ }
+ }
+ getAllTipNodesFromInternalNode(parent);
+ for(int j=0;j<tempNs.size();j++){
+ //System.out.println("- "+tempNs.get(j).getName());
+ }
+ //trees.get(i).pruneExternalNode(trees.get(i).getExternalNode(tipname));
+ //parent = parent.getParent();
+ getAllTipNodesFromInternalNode(parent);
+ ArrayList<String> arr = new ArrayList<String>();
+ for(int j=0;j<tempNs.size();j++){
+ if(tempNs.get(j)!=trees.get(i).getExternalNode(tipname)){
+ arr.add(tempNs.get(j).getName());
+ //System.out.println("+ "+tempNs.get(j).getName());
+ }
+ }
+ Node n = mrct.getMRCA(arr);
+ if(values.get((Node)n) == null){
+ values.put((Node)n, 1.0);
+ x++;
+ }else{
+ values.put((Node)n, values.get((Node)n)+1.0);
+ x++;
+ }
+ }
+ Iterator it= values.keySet().iterator();
+ while(it.hasNext()){
+ Node a = ((Node)it.next());
+ a.setName(a.getName()+"_"+(values.get((Node)a)/x));
+ }
+ //System.out.println(mrct.getRoot().getNewick(false)+";");
+ return mrct;
+ }
+
+ ArrayList<Node> tempNs;
+ private void getAllTipNodesFromInternalNode(Node intree){
+ tempNs = new ArrayList<Node>();
+ poGATNFIN(intree);
+ }
+
+ private void poGATNFIN(Node innode){
+ for(int i=0;i<innode.getChildCount();i++){
+ poGATNFIN(innode.getChild(i));
+ }
+ if(innode.isExternal()==true){
+ tempNs.add(innode);
+ }
+ }
+
+ private void readMRCTREE(){
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ try{
+ br = new BufferedReader (new FileReader(mrcf));
+ String str = "";
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ mrct = tr.readTree();
+ }
+ }catch (FileNotFoundException e){}catch(IOException ioe){}
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ if(args.length <3){
+ System.out.println("trees contree taxon");
+ System.out.println("or");
+ System.out.println("trees contree taxon prune");
+ }else if(args.length==3){
+ Main m = new Main(args[0], args[1], args[2]);
+ m.readTrees();
+ m.run();
+ }else if(args.length == 4){
+ Main m = new Main(args[0], args[1], args[2], "prune");
+ m.readTrees();
+ m.run();
+ }else{
+ System.out.println("trees contree taxon");
+ System.out.println("or");
+ System.out.println("trees contree taxon prune");
+ }
+ }
+
+}
diff --git a/src/phyutility/mainrunner/Main.java b/src/phyutility/mainrunner/Main.java
new file mode 100644
index 0000000..5900441
--- /dev/null
+++ b/src/phyutility/mainrunner/Main.java
@@ -0,0 +1,1566 @@
+package phyutility.mainrunner;
+
+import jade.tree.TreeReader;
+
+import java.util.*;
+import java.io.*;
+
+import jebl.evolution.io.ImportException;
+import phyutility.concat.Concat;
+import phyutility.drb.WwdEmbedded;
+
+public class Main {
+ private boolean treesupp = false; //-ts
+ private boolean linmovement = false; //-lm requires lmt
+ private boolean prune = false; //use -names for which to prune
+ private String tree = null; //-tree
+ private boolean leafstab = false; //-ls
+ private boolean convert = false; //-vert for newick to nex or nex to nex no trans
+ private boolean consensus = false; //-con requires threshold
+ private boolean out_oth = false; //-othout
+ private double threshold = 0.0; //-t
+ private boolean reroot = false; //-rr
+ private boolean thinner = false;//-tt # every
+ private double thin = 10;
+ private boolean ltt = false;
+ private boolean derb = false;
+ //sequence functions
+ private boolean concat = false;
+ private boolean clean = false;
+ private String seqtypeset = "test";
+ private double cleannum = 0.5;
+ private boolean cleanmessy = false;
+ private double cleanmessynum = 0.1;
+ private boolean parse = false;
+ private int parsenum = 1;
+ private boolean ncbisearch = false;
+ private String sterm = "";
+ private int sdb = 1;//nuc = 1, prot = 2, genome = 3, tax = 4
+ private boolean ncbiget = false;
+ private String outfor = "31";
+ private String sep = "_";
+ private int lengthlim;
+ private boolean blast = false;//not implemented yet
+
+ //db
+ private WwdEmbedded db;
+
+
+ private ArrayList<String> mrcanames; //-names
+ private String logfile = null; //-log
+ private String outfile = null; //-out
+ private ArrayList<String> infiles; //-in
+ private FileWriter fw;
+
+ public Main(String [] args){
+ if(args.length == 0){
+ printUsage();
+ }else if(args.length == 1 ){
+ if(args[0].toUpperCase().compareTo("-I")== 0){
+ goInteractive();
+ runArgs();
+ }
+ else{
+ processArgs(args);
+ runArgs();
+ }
+ }else{
+ processArgs(args);
+ runArgs();
+ }
+ }
+
+ private void processArgs(String [] args){
+ /*
+ * catch the secret functions
+ */
+ if(args[0].toLowerCase().compareTo("-sec")==0){
+ TestFuncs tf = new TestFuncs(args);
+ System.exit(0);
+ }
+ /*
+ * end catch the secrets
+ */
+ //get help
+ for(int i=0;i<args.length;i++){
+ if(args[i].toLowerCase().compareTo("-h")==0){
+ i++;
+ if(args.length == i){
+ System.err.println("you have to enter a command name after -h");
+ printUsage();
+ System.exit(0);
+ }
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a command name after -h");
+ printUsage();
+ System.exit(0);
+ }else{
+ printHelp( args[i]);
+ }
+ System.exit(0);
+ }
+ }
+ //get log
+ for(int i=0;i<args.length;i++){
+ if(args[i].toLowerCase().compareTo("-log")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a log filename after -log");
+ System.exit(0);
+ }else{
+ logfile = args[i];
+ }
+ }
+ }
+ startLogging();
+ for(int i=0;i<args.length;i++){
+ if(args[i].toLowerCase().compareTo("-lm")==0){
+ linmovement = true;
+ log("lineage movement\n");
+ }else if(args[i].toLowerCase().compareTo("-derb")==0){
+ //derb = true;
+ derb = false;
+ log("trying to use derby database for trees\n");
+ log("DERBY NO LONGER SUPPORTED [not free]\n");
+ System.exit(0);
+ }else if(args[i].toLowerCase().compareTo("-ts")==0){
+ treesupp = true;
+ log("tree support\n");
+ }else if(args[i].toLowerCase().compareTo("-ls")==0){
+ leafstab = true;
+ log("leaf stability\n");
+ }else if(args[i].toLowerCase().compareTo("-rr")==0){
+ reroot = true;
+ log("reroot\n");
+ }else if(args[i].toLowerCase().compareTo("-vert")==0){
+ convert = true;
+ log("tree convert\n");
+ }else if(args[i].toLowerCase().compareTo("-con")==0){
+ consensus = true;
+ log("consensus\n");
+ }else if(args[i].toLowerCase().compareTo("-lmt")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a consensus filename after -lmt");
+ System.exit(0);
+ }else{
+ tree = args[i];
+ log("tree filename "+args[i]+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-tree")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a tree filename after -tree");
+ System.exit(0);
+ }else{
+ tree = args[i];
+ log("tree filename "+args[i]+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-pr")==0){
+ prune = true;
+ }else if(args[i].toLowerCase().compareTo("-t")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a threshhold after -t");
+ System.exit(0);
+ }else{
+ threshold = Double.valueOf(args[i]);
+ }
+ }else if(args[i].toLowerCase().compareTo("-tt")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a thinning number after -tt");
+ System.exit(0);
+ }else{
+ thin = Double.valueOf(args[i]);
+ thinner = true;
+ }
+ }else if(args[i].toLowerCase().compareTo("-names")==0){
+ mrcanames = new ArrayList<String>();
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter taxon names after -names");
+ System.exit(0);
+ }else{
+ while(args[i].trim().startsWith("-")==false){
+ mrcanames.add(args[i]);
+ log("mrca: "+args[i]+"\n");
+ i++;
+ if(i >= args.length){
+ break;
+ }
+ }
+ i--;
+ }
+ }
+ //sequence
+ else if(args[i].toLowerCase().compareTo("-concat")==0){
+ concat = true;
+ log("concat\n");
+ }else if(args[i].toLowerCase().compareTo("-clean")==0){
+ clean = true;
+ log("clean\n");
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a cleannum after -clean");
+ System.exit(0);
+ }else{
+ cleannum = Double.valueOf(args[i]);
+ log("cleannum: "+cleannum+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-cleanmessy")==0){
+ log("cleanmessy\n");
+ cleanmessy = true;
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a cleannum after -cleanmessy");
+ System.exit(0);
+ }else{
+ cleanmessynum = Double.valueOf(args[i]);
+ log("cleanmessynum: "+cleanmessynum+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-aa") == 0){
+ seqtypeset = "aa";
+ }else if(args[i].toLowerCase().compareTo("-parse")==0){
+ parse = true;
+ log("parse\n");
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a parsenum after -parse");
+ System.exit(0);
+ }else{
+ parsenum = Integer.valueOf(args[i]);
+ log("parsenum: "+parsenum+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-es")==0){
+ ncbisearch = true;
+ log("ncbisearch\n");
+ }else if(args[i].toLowerCase().compareTo("-term")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a search term after -term");
+ System.exit(0);
+ }else{
+ sterm = args[i];
+ log("term: "+sterm+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-db")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a database number after -db");
+ System.exit(0);
+ }else{
+ sdb = Integer.valueOf(args[i]);
+ log("database: "+sdb+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-ef")==0){
+ ncbiget = true;
+ log("ncbiget\n");
+ }else if(args[i].toLowerCase().compareTo("-ll")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a length limit after -ll");
+ System.exit(0);
+ }else{
+ this.lengthlim = Integer.valueOf(args[i]);
+ log("length lim: "+this.lengthlim+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-outfor")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter numbers after -outfor");
+ System.exit(0);
+ }else{
+ outfor = args[i];
+ log("outfor: "+outfor+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-sep")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a seperator after -sep");
+ System.exit(0);
+ }else{
+ sep = args[i];
+ log("sep: "+sep+"\n");
+ }
+ }
+ //other
+ else if(args[i].toLowerCase().compareTo("-othout")==0){
+ out_oth = true;
+ }else if(args[i].toLowerCase().compareTo("-out")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a out filename after -out");
+ System.exit(0);
+ }else{
+ outfile = args[i];
+ log("outfile: "+outfile+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-in")==0){
+ infiles = new ArrayList<String>();
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter filenames after -in");
+ System.exit(0);
+ }else{
+ while(args[i].trim().startsWith("-")==false){
+ infiles.add(args[i]);
+ log("in filename: "+args[i]+"\n");
+ i++;
+ if(i >= args.length){
+ break;
+ }
+ }
+ i--;
+ }
+ }else if(args[i].toLowerCase().compareTo("-log")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a out filename after -log");
+ System.exit(0);
+ }
+ }else{
+ System.out.println("don't recognize argument "+args[i]);
+ }
+ }
+ }
+
+ private void goInteractive(){
+
+ }
+
+ private void runArgs(){
+ int x = 0;
+ if(linmovement == true){
+ x++;
+ }if(treesupp == true){
+ x++;
+ }if(leafstab == true){
+ x++;
+ }if(convert == true){
+ x++;
+ }if(consensus == true){
+ x++;
+ }if(reroot == true){
+ x++;
+ }if(thinner == true){
+ x++;
+ }if(prune == true){
+ x++;
+ }
+ //sequence
+ if(concat == true){
+ x++;
+ }if(parse == true){
+ x++;
+ }if(clean == true){
+ x++;
+ }if(ncbisearch == true){
+ x++;
+ }if(ncbiget == true){
+ x++;
+ }if(blast == true){
+ x++;
+ }
+ if(x == 0){
+ System.out.println("you have to enter some sort of analysis");
+ printUsage();
+ System.exit(0);
+ }if(x > 1){
+ System.out.println("you can only do one thing at a time");
+ printUsage();
+ System.exit(0);
+ }
+ if(infiles == null && (ncbisearch == false && ncbiget == false)){
+ System.out.println("you have to enter some infiles (-in)");
+ printUsage();
+ System.exit(0);
+ }
+ if(linmovement == true){
+ lineageMove();
+ stopLogging();
+ System.exit(0);
+ }if(treesupp == true){
+ treeSupp();
+ stopLogging();
+ System.exit(0);
+ }if(leafstab == true){
+ leafStab();
+ System.exit(0);
+ }if(convert == true){
+ convert();
+ System.exit(0);
+ }if(consensus == true){
+ consensus();
+ System.exit(0);
+ }if(reroot == true){
+ reroot();
+ System.exit(0);
+ }if(thinner == true){
+ thin();
+ System.exit(0);
+ }if(prune == true){
+ prune();
+ System.exit(0);
+ }
+ //sequence
+ if(concat == true){
+ concat();
+ System.exit(0);
+ }if(parse == true){
+ parse();
+ System.exit(0);
+ }if(clean == true){
+ clean();
+ System.exit(0);
+ }if(ncbisearch == true){
+ ncbisearch();
+ System.exit(0);
+ }if(ncbiget == true){
+ ncbiget();
+ System.exit(0);
+ }if(blast == true){
+ blast();
+ System.exit(0);
+ }
+ }
+
+ /*
+ * tree tools
+ */
+
+ private void lineageMove(){
+ if(tree == null){
+ System.out.println("you have to enter a consensus tree when performing a lineage movement analysis (-tree)");
+ System.exit(0);
+ }
+ if(mrcanames == null){
+ System.out.println("you have to enter MRCA or tip names when performing a lineage movement analysis (-names)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ intrees.add(tr.readTree());
+ }
+ br.close();
+ }catch(IOException ioe){};
+ }
+ }
+ log("reading in consensus tree\n");
+ boolean nex = false;
+ jade.tree.Tree contr = null;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(tree));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ nex = true;
+ br.close();
+ }catch(IOException ioe){}
+ if(nex == true){
+ contr = getNewickFromNexus(tree).get(0);
+ }else{
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(str);
+ contr = tr.readTree();
+ }
+ phyutility.lineagemovement.Main lm = new phyutility.lineagemovement.Main(intrees,contr,mrcanames);
+ if(outfile == null){
+ System.out.println(lm.run().getRoot().getNewick(true));
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && nex == true && out_oth != true)||
+ (testnex == false || nex == false && out_oth == true)){
+ String nw = lm.run().getRoot().getNewick(true)+";";
+ ArrayList<String > nwa = new ArrayList<String>();
+ nwa.add(nw);
+ Utils.newickToNexus(outfile, nwa);
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ outw.write(lm.run().getRoot().getNewick(true)+";");
+ outw.close();
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private void treeSupp(){
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ intrees.add(tr.readTree());
+ }
+ br.close();
+ }catch(IOException ioe){};
+ }
+ }
+ log("reading in tree\n");
+ boolean nex = false;
+ jade.tree.Tree contr = null;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(tree));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ nex = true;
+ br.close();
+ }catch(IOException ioe){}
+ if(nex == true){
+ contr = getNewickFromNexus(tree).get(0);
+ }else{
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(str);
+ contr = tr.readTree();
+ }
+ phyutility.treesupport.Runner ls = new phyutility.treesupport.Runner(intrees, contr);
+ if(outfile == null){
+ System.out.println(ls.run().getRoot().getNewick(true)+";");
+ log("finished tree support\n");
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && nex == true && out_oth != true)||
+ (testnex == false || nex == false && out_oth == true)){
+ String nw = ls.run().getRoot().getNewick(true)+";";
+ ArrayList<String > nwa = new ArrayList<String>();
+ nwa.add(nw);
+ Utils.newickToNexus(outfile, nwa);
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ outw.write(ls.run().getRoot().getNewick(true)+";");
+ outw.close();
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private void leafStab(){
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ for(int i=0;i<infiles.size();i++){
+ if(testForNexus(infiles.get(i))){
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ intrees.add(tr.readTree());
+ }
+ br.close();
+ }catch(IOException ioe){};
+ }
+ }
+ phyutility.leafstability.Runner ls = new phyutility.leafstability.Runner(intrees);
+ ls.run();
+ ls.printResults();
+ }
+
+ //nexus to newick
+ //nexus to nexus tr with othout
+ //newick to nexus
+ //newick to nexus tr with othout
+ private void convert(){
+ ArrayList<jebl.evolution.trees.Tree> alltrees = new ArrayList<jebl.evolution.trees.Tree>();
+ if(infiles.size() > 1){
+ System.err.println("Converting trees is done one file at a time, only the first file will be used.");
+ }
+ if(outfile == null){
+ System.err.println("You must enter an outfile (-outfile).");
+ System.exit(0);
+ }
+ if(testForNexus(infiles.get(0))){
+ jebl.evolution.io.NexusImporter ni;
+ try {
+ ni = new jebl.evolution.io.NexusImporter(new FileReader(infiles.get(0)));
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ if(out_oth == true){
+ FileWriter fw;
+ try {
+ fw = new FileWriter(outfile);
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ HashMap <String, String> map = new HashMap<String, String>();
+ Set<jebl.evolution.taxa.Taxon> tax = alltrees.get(0).getTaxa();
+ Iterator<jebl.evolution.taxa.Taxon> it = tax.iterator();
+ int i=1;
+ while(it.hasNext()){
+ map.put(String.valueOf(i),it.next().getName());
+ }
+ ne.exportTreesWithTranslation(alltrees,map);
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ try {
+ BufferedWriter fw = new BufferedWriter (new FileWriter(outfile));
+ jebl.evolution.io.NewickExporter ne = new jebl.evolution.io.NewickExporter(fw);
+ ne.exportTrees(alltrees);
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }else{
+ jebl.evolution.io.NewickImporter ni;
+ try {
+ ni = new jebl.evolution.io.NewickImporter(new FileReader(infiles.get(0)), true);
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ if(out_oth == true){
+ FileWriter fw;
+ try {
+ fw = new FileWriter(outfile);
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ HashMap <String, String> map = new HashMap<String, String>();
+ Set<jebl.evolution.taxa.Taxon> tax = alltrees.get(0).getTaxa();
+ Iterator<jebl.evolution.taxa.Taxon> it = tax.iterator();
+ int i=1;
+ while(it.hasNext()){
+ map.put(String.valueOf(i),it.next().getName());
+ }
+ ne.exportTreesWithTranslation(alltrees,map);
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ try {
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTrees(alltrees);
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+ private void consensus(){
+ ArrayList<jebl.evolution.trees.Tree> intrees = new ArrayList<jebl.evolution.trees.Tree>();
+ ArrayList<jebl.evolution.trees.Tree> alltrees = new ArrayList<jebl.evolution.trees.Tree>();
+ boolean rooted = false;
+ for(int i=0;i<infiles.size();i++){
+ if(testForNexus(infiles.get(0))){
+ jebl.evolution.io.NexusImporter ni;
+ try {
+ ni = new jebl.evolution.io.NexusImporter(new FileReader(infiles.get(0)));
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ if(getJadeTreeFromJeblTree(alltrees.get(0)).getRoot().getChildCount()>2){
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ rooted = true;
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ alltrees = null;
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ jebl.evolution.io.NewickImporter ni;
+ try {
+ ni = new jebl.evolution.io.NewickImporter(new FileReader(infiles.get(0)), true);
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ if(getJadeTreeFromJeblTree(alltrees.get(0)).getRoot().getChildCount()>2){
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ rooted = true;
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ alltrees = null;
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ if(rooted == true){
+ System.out.println("rooted");
+ jebl.evolution.trees.RootedTree [] tr = new jebl.evolution.trees.RootedTree[intrees.size()];
+ for(int i=0;i<tr.length;i++){
+ tr[i] = (jebl.evolution.trees.RootedTree)intrees.get(i);
+ }
+ intrees = null;
+ jebl.evolution.trees.GreedyRootedConsensusTreeBuilder con = new jebl.evolution.trees.GreedyRootedConsensusTreeBuilder(tr, threshold);
+ if(outfile == null){
+ log("place an outfile to get internal node values (-out)\n");
+ System.out.println(jebl.evolution.trees.Utils.toNewick(con.build()));
+ }else{
+ try {
+ if(out_oth == true){
+ File toutf = new File(outfile+"temp");
+ BufferedWriter fw = new BufferedWriter(new FileWriter(toutf));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ ArrayList<jade.tree.Tree> ttrees = getNewickFromNexus(outfile+"temp");
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<ttrees.size();i++){
+ outw.write(ttrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ toutf.delete();
+ }else{
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }else{
+ jebl.evolution.trees.Tree [] tr = new jebl.evolution.trees.Tree[intrees.size()];
+ for(int i=0;i<tr.length;i++){
+ tr[i] = intrees.get(i);
+ }
+ intrees = null;
+ jebl.evolution.trees.GreedyUnrootedConsensusTreeBuilder con = new jebl.evolution.trees.GreedyUnrootedConsensusTreeBuilder(tr, null, threshold);
+ if(outfile == null){
+ log("place an outfile to get internal node values (-out)\n");
+ System.out.println(jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)con.build()));
+ }else{
+ try {
+ if(out_oth == true){
+ File toutf = new File(outfile+"temp");
+ BufferedWriter fw = new BufferedWriter(new FileWriter(toutf));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ ArrayList<jade.tree.Tree> ttrees = getNewickFromNexus(outfile+"temp");
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<ttrees.size();i++){
+ outw.write(ttrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ toutf.delete();
+ }else{
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void reroot(){
+ if(mrcanames == null){
+ System.out.println("you not entered MRCA or tip names when performing a reroot analysis (-names), so phyutility will attempt an unroot");
+ //System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ WwdEmbedded dbtemp = getNewickFromNexusDerby(infiles.get(i));
+ for(int j=0;j<dbtemp.getTableTreeSize();j++){
+ db.addTree(dbtemp.getTree(j)+";");
+ }
+ }else{
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ }
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ if(derb == true){
+ if(str.length()>1)
+ db.addTree(tr.readTree().getRoot().getNewick(true)+";");
+ }
+ else
+ intrees.add(tr.readTree());
+ }
+ if(derb ==true){
+ System.out.println("read "+db.getTableTreeSize());
+ }
+ br.close();
+ }catch(IOException ioe){}
+ }
+ }
+ if(outfile == null){
+ for(int i=0;i<intrees.size();i++){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ System.out.println(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ log("finished reroot\n");
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && out_oth != true)||
+ (testnex == false && out_oth == true)){
+ if(derb == true){
+ if(mrcanames != null){
+ Utils.newickToNexusRerootDerby(outfile, db, mrcanames);
+ }else{
+ Utils.newickToNexusUnrootDerby(outfile, db);
+ }
+ this.deleteDatabaseFolder(new File("null"));
+ }else{
+ ArrayList<String > nwa = new ArrayList<String>();
+ for(int i=0;i<intrees.size();i++){
+ if(mrcanames != null){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ }else{
+ intrees.get(i).unRoot(intrees.get(i).getRoot());
+ }
+ nwa.add(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ Utils.newickToNexus(outfile, nwa);
+ }
+ }else{
+ if(derb == true){
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<db.getTableTreeSize();i++){
+ jade.tree.Tree x = db.getJadeTree(i);
+ if(mrcanames != null){
+ x.reRoot(x.getMRCA(mrcanames));
+ }else{
+ x.reRoot(x.getRoot());
+ }
+ outw.write(x.getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ this.deleteDatabaseFolder(new File("null"));
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<intrees.size();i++){
+ if(mrcanames != null){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ }else{
+ intrees.get(i).unRoot(intrees.get(i).getRoot());
+ }
+ outw.write(intrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ }
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private void thin(){
+ log("thinning trees\n");
+ ArrayList<jebl.evolution.trees.Tree> alltrees = new ArrayList<jebl.evolution.trees.Tree>();
+ ArrayList<jebl.evolution.trees.Tree> thtrees = new ArrayList<jebl.evolution.trees.Tree>();
+ if(infiles.size() > 1){
+ System.err.println("Converting trees is done one file at a time, only the first file will be used.");
+ }
+ if(outfile == null){
+ System.err.println("You must enter an outfile (-outfile).");
+ System.exit(0);
+ }
+ log("thinning every "+thin+"th tree\n");
+ if(testForNexus(infiles.get(0))){
+ jebl.evolution.io.NexusImporter ni;
+ try {
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ WwdEmbedded dbtemp = getNewickFromNexusDerby(infiles.get(0));
+ for(int i=0;i<dbtemp.getTableTreeSize();i++){
+ if(i%thin == 0){
+ db.addTree(dbtemp.getTree(i)+";");
+ }
+ }
+ }else{
+ ni = new jebl.evolution.io.NexusImporter(new FileReader(infiles.get(0)));
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ for(int i=0;i<alltrees.size();i++){
+ if(i%thin == 0){
+ thtrees.add(alltrees.get(i));
+ }
+ }
+ alltrees = null;
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ try {
+ if(derb == true){
+ Utils.newickToNexusDerby(outfile, db);
+ }else{
+ BufferedWriter fw = new BufferedWriter (new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTrees(thtrees);
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ jebl.evolution.io.NewickImporter ni;
+ try {
+ if(derb == true){
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(0)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ int i=0;
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ if(str.length()>1 && i%thin == 0)
+ db.addTree(tr.readTree().getRoot().getNewick(true)+";");
+ i++;
+ }
+ br.close();
+ }catch(IOException ioe){}
+ }else{
+ ni = new jebl.evolution.io.NewickImporter(new FileReader(infiles.get(0)), true);
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ for(int i=0;i<alltrees.size();i++){
+ if(i%thin == 0){
+ thtrees.add(alltrees.get(i));
+ }
+ }
+ alltrees = null;
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ try {
+ if(derb == true){
+ FileWriter fw = new FileWriter(outfile);
+ for(int i=0;i<db.getTableTreeSize();i++){
+ jade.tree.Tree x = db.getJadeTree(i);
+ fw.write(x.getRoot().getNewick(true)+";\n");
+ }
+ fw.close();
+ this.deleteDatabaseFolder(new File("null"));
+ }else{
+ BufferedWriter fw = new BufferedWriter (new FileWriter(outfile));
+ jebl.evolution.io.NewickExporter ne = new jebl.evolution.io.NewickExporter(fw);
+ ne.exportTrees(thtrees);
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void prune(){
+ if(mrcanames == null){
+ System.out.println("you have to enter tip names when performing a pruning analysis (-names)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ intrees.add(tr.readTree());
+ }
+ br.close();
+ }catch(IOException ioe){};
+ }
+ }
+ if(outfile == null){
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ pr.go();
+ for(int i=0;i<intrees.size();i++){
+ System.out.println(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ log("finished pruning\n");
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && out_oth != true)||
+ (testnex == false && out_oth == true)){
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ intrees = pr.go();
+ ArrayList<String > nwa = new ArrayList<String>();
+ for(int i=0;i<intrees.size();i++){
+ nwa.add(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ Utils.newickToNexus(outfile, nwa);
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ intrees = pr.go();
+ for(int i=0;i<intrees.size();i++){
+ outw.write(intrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private ArrayList<jade.tree.Tree> getNewickFromNexus(String filename){
+ log("reading in nexus trees\n");
+ return phyutility.jebl2jade.NexusToJade.getJadeFromJeblNexus(filename);
+ }
+
+ private WwdEmbedded getNewickFromNexusDerby(String filename){
+ log("reading in nexus trees to derby\n");
+ return phyutility.jebl2jade.NexusToJade.getJadeFromJeblNexusDerby(filename);
+ }
+
+ private jade.tree.Tree getJadeTreeFromJeblTree(jebl.evolution.trees.Tree intr){
+ jade.tree.Tree rettr = null;
+ String st = jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)intr);
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ br = new BufferedReader (new StringReader(st));
+ String str = "";
+ try {
+ while((str = br.readLine())!=null){
+ tr.setTree(str+";");
+ rettr = tr.readTree();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return rettr;
+ }
+
+ /*
+ * sequence tools
+ */
+
+ /*
+ * takes fasta or nexus input (aligned)
+ * will output fasta or nexus (aligned)
+ * assumes different files are different genes
+ */
+ private void concat(){
+ String seqtype = "test";
+ if(seqtypeset == "aa"){
+ seqtype = "aa";
+ }else if (seqtypeset == "nucleotide"){
+ seqtype = "nucleotide";
+ }
+ Concat cc = new Concat(infiles,seqtype);
+ if(outfile != null){
+ if(out_oth == true){
+ cc.printtofileFASTA(outfile);
+ }else{//nexus
+ cc.printtofileNEXUS(outfile);
+ }
+ }else{
+ cc.printtoscreenNEXUS();
+ }
+ }
+
+ /*
+ * takes fasta as input (unaligned)
+ * will output fasta (unaligned)
+ * System.out.println("\tthis assumes that the file is a genbank fasta file\n" +
+ "\tthe numbers correspond to these options for the names\n" +
+ "\t1) gi number\n" +
+ "\t2) gb number\n" +
+ "\t3) taxon name\n" +
+ "\t4) taxon_name\n" +
+ "\t5) T_name\n" +
+ "\t6) taxon_name_ginumber\n" +
+ "\t7) taxon_name_gbnumber\n" +
+ "\t8) formatting for bioorganizer so type java -jar gbparser.jar 8 file regionname\n"+
+ "\n" +
+ "NOTE 1: if there are duplicate names, this will add numbers to the end\n" +
+ "NOTE 2: some genbank entries are poorly formed and therefore do not get formatted correctly here, so double check!");
+ */
+ private void parse(){
+ if(parsenum != 666){
+ if(outfile != null){
+ FileWriter fw;
+ try {
+ fw = new FileWriter(outfile);
+ for(int i=0;i<infiles.size();i++){
+ jade.data.GBParser ca = new jade.data.GBParser(infiles.get(i), parsenum);
+ ArrayList<jade.data.Sequence> seqs = ca.getSeqs();
+ for(int j=0;j<seqs.size();j++){
+ fw.write(">"+seqs.get(j).getID()+"\n");
+ fw.write(seqs.get(j).getSeq()+"\n\n");
+ }
+ }
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ for(int i=0;i<infiles.size();i++){
+ jade.data.GBParser ca;
+ try {
+ ca = new jade.data.GBParser(infiles.get(i), parsenum);
+ ArrayList<jade.data.Sequence> seqs = ca.getSeqs();
+ for(int j=0;j<seqs.size();j++){
+ System.out.println(">"+seqs.get(j).getID());
+ System.out.println(seqs.get(j).getSeq());
+ System.out.println();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }else{
+
+ }
+ }
+
+ /*
+ * takes fasta or nexus input (aligned)
+ * one file at a time
+ * will output fasta or nexus (aligned)
+ */
+ private void clean(){
+ if(infiles.size() > 1){
+ System.err.println("Trimming is done one file at a time, only the first file will be used.");
+ }
+ String seqtype = "test";
+ if(seqtypeset == "aa"){
+ seqtype = "aa";
+ }else if (seqtypeset == "nucleotide"){
+ seqtype = "nucleotide";
+ }
+ phyutility.trimsites.TrimSites ts = new phyutility.trimsites.TrimSites(infiles.get(0),seqtype);
+ ts.trimAln(cleannum);
+ if(cleanmessy == true){
+ ts.trimAlnCleanMessy(cleanmessynum);
+ }
+ if(testForNexus(infiles.get(0))){
+ if(out_oth == true){
+ ts.printFastaOutfile(outfile);
+ }else{
+ ts.printNexusOutfile(outfile);
+ }
+ }else{
+ if(out_oth == false){
+ ts.printFastaOutfile(outfile);
+ }else{
+ ts.printNexusOutfile(outfile);
+ }
+ }
+ }
+
+ /*
+ * incomplete
+ */
+
+ private void ncbisearch(){
+ if(sterm == "" || sterm.length() < 2){
+ System.out.println("in order to search ncbi you much enter a search term (-term)");
+ }
+ phyutility.ncbi.Einfo einfo = new phyutility.ncbi.Einfo();
+ if(sdb == 1){
+ ArrayList<String> ids = einfo.search("nucleotide", sterm);
+ log("Count = "+ids.size()+"\n");
+ log("genbankids\n-----------\n");
+ for(int i=0;i<ids.size();i++){
+ log(ids.get(i)+"\n");
+ }
+ System.out.println("Count = "+ids.size());
+ }else if(sdb == 2){
+ ArrayList<String> ids = einfo.search("protein", sterm);
+ log("Count = "+ids.size()+"\n");
+ log("genbankids\n-----------\n");
+ for(int i=0;i<ids.size();i++){
+ log(ids.get(i)+"\n");
+ }
+ System.out.println("Count = "+ids.size());
+ }else if(sdb == 3){
+ ArrayList<String> ids = einfo.search("genome", sterm);
+ log("Count = "+ids.size()+"\n");
+ log("genbankids\n-----------\n");
+ for(int i=0;i<ids.size();i++){
+ log(ids.get(i)+"\n");
+ }
+ System.out.println("Count = "+ids.size());
+ }else if(sdb == 4){
+ ArrayList<String> ids = einfo.search("taxonomy", sterm);
+ log("Count = "+ids.size()+"\n");
+ log("genbankids\n-----------\n");
+ for(int i=0;i<ids.size();i++){
+ log(ids.get(i)+"\n");
+ }
+ System.out.println("Count = "+ids.size());
+ }else if(sdb == 5){
+ ArrayList<String> ids = einfo.search("nuccore", sterm);
+ log("Count = "+ids.size()+"\n");
+ log("genbankids\n-----------\n");
+ for(int i=0;i<ids.size();i++){
+ log(ids.get(i)+"\n");
+ }
+ System.out.println("Count = "+ids.size());
+ }
+
+ }
+
+ private void ncbiget(){
+ if(sterm == "" || sterm.length() < 2){
+ System.out.println("in order to fetch from ncbi you much enter a search term (-term)");
+ }
+ phyutility.ncbi.Einfo einfo = new phyutility.ncbi.Einfo();
+ if(sdb == 1){
+ einfo.efetch("nucleotide", sterm, this.lengthlim, outfile, outfor, sep);
+ }else if(sdb == 2){
+ einfo.efetch("protein", sterm, this.lengthlim, outfile, outfor, sep);
+ }else if(sdb == 3){
+ einfo.efetch("genome", sterm, this.lengthlim, outfile, outfor, sep);
+ }else if(sdb == 4){
+ System.out.println("sorry, no taxonomy downloads");
+ }
+ }
+
+ /*
+ * incomplete
+ * going to require blastcl3 or blast2, can i bundle this?
+ */
+ private void blast(){
+
+ }
+
+
+ /*
+ * utils
+ */
+ private boolean testForNexus(String filename){
+ boolean ret = false;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ ret = true;
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+
+ private boolean deleteDatabaseFolder(File dir){
+ if (dir.isDirectory()) {
+ String[] children = dir.list();
+ for (int i=0; i<children.length; i++) {
+ boolean success = deleteDatabaseFolder(new File(dir, children[i]));
+ if (!success) {
+ return false;
+ }
+ }
+ }
+
+ // The directory is now empty so delete it
+ return dir.delete();
+ }
+
+ private void startLogging(){
+ try {
+ if(logfile == null){
+ /*java.util.Date dt = new java.util.Date();
+ java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("dd.MM.yy.hh.mm");
+ String datenewformat = formatter.format(dt);
+ logfile = datenewformat+".txt";*/
+ logfile = "phyutility.log";
+ }
+ fw = new FileWriter(logfile,true);
+ java.util.Date dt = new java.util.Date();
+ java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("dd/MM/yy hh:mm");
+ String datenewformat = formatter.format(dt);
+ log("starting log: "+datenewformat+"\n");
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void log(String instring){
+ try {
+ fw.write(instring);
+ fw.flush();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void stopLogging(){
+ try {
+ fw.flush();
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void printUsage(){
+ System.out.println("Phyutility (fyoo-til-i-te) v.2.2.6");
+ System.out.println("Stephen A. Smith http://www.blackrim.org eebsmith at umich.edu");
+ System.out.println("help on a specific command use option -h <command>");
+ System.out.println("commands:");
+ System.out.println("trees:");
+ System.out.println(" consensus");
+ System.out.println(" convert");
+ System.out.println(" leafstab");
+ System.out.println(" linmove");
+ System.out.println(" prune");
+ System.out.println(" reroot");
+ System.out.println(" thin");
+ System.out.println(" treesupp");
+ System.out.println("seqs:");
+ System.out.println(" clean");
+ System.out.println(" concat");
+ System.out.println(" ncbiget");
+ System.out.println(" ncbisearch");
+ System.out.println(" parse");
+ System.out.println("see documentation for more information");
+ }
+
+ /*
+ * prints a helpful comment with instructions and examples for
+ * all the commands (usage)
+ */
+ private void printHelp(String cmd){
+ if(cmd.compareTo("consensus") == 0){
+ System.out.println("makes consensus trees from single or multiple files");
+ System.out.println("options:");
+ System.out.println(" -con | designates that you want to make a consensus");
+ System.out.println(" -t <number> | threshold for consensus (1.0 = strict, 0.5 = majrule, 0 = allcompat)");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -con -t 0.5 -in testall.tre -out test.con");
+ }else if(cmd.compareTo("convert") == 0){
+ System.out.println("converts between tree file types");
+ System.out.println("options:");
+ System.out.println(" -vert | designates that you want to convert");
+ System.out.println(" -in <file name> | input file name");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -vert -in test.tre -out testvert.nex");
+ }else if(cmd.compareTo("leafstab") == 0){
+ System.out.println("calculates leaf stability for all tips");
+ System.out.println("options:");
+ System.out.println(" -ls | designates that you want to do leaf stability index calculations");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println("java -jar phyutility.jar -ls -in testall.tre");
+ }else if(cmd.compareTo("linmove") == 0){
+ System.out.println("conducts lineage movement procedure on tree");
+ System.out.println("options:");
+ System.out.println(" -lm | designates that you want to run lineage movement analysis");
+ System.out.println(" -names <tip name> ... | names to check -- multiple form clade");
+ System.out.println(" -tree <file name> | consensus file to map movement to");
+ System.out.println(" -in <file name> ... | input tree file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -lm -in testall.tre -tree test.con -out testlm.tre -names three");
+ }else if(cmd.compareTo("prune") == 0){
+ System.out.println("prunes tips or clades -- has problems if node to prune is root");
+ System.out.println("options:");
+ System.out.println(" -pr | designates that you want to prune");
+ System.out.println(" -names <tip name> ... | names to check -- multiple form clade");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -pr -in test.tre -out testpr.tre -names one");
+ }else if(cmd.compareTo("reroot") == 0){
+ System.out.println("reroots one or multiple trees");
+ System.out.println("options:");
+ System.out.println(" -rr | designates that you want to reroot");
+ System.out.println(" -names <tip name> ... | names to check -- multiple form clade");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -rr -in test.tre -out testrr.tre -names one two");
+ }else if(cmd.compareTo("thin") == 0){
+ System.out.println("thins tree files");
+ System.out.println("options:");
+ System.out.println(" -tt # | designates that you want to thin followed by the number of thinning (sample every #)");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -tt 100 -in testall.tre -out testts.tre");
+ }else if(cmd.compareTo("treesupp") == 0){
+ System.out.println("allows to calculate support for a tree given a set of trees");
+ System.out.println("options:");
+ System.out.println(" -ts | designates that you want to calculate support for a tree given a set of trees");
+ System.out.println(" -tree <file name> | tree to get support for");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -ts -in testall.tre -tree test.con -out testts.tre");
+ }else if(cmd.compareTo("clean") == 0){
+ System.out.println("trims sequences based on a threshhold");
+ System.out.println("options:");
+ System.out.println(" -clean # | designates that you want to trim and the threshold must follow");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println(" -aa | force amino acid files");
+ System.out.println("java -jar phyutility.jar -clean 0.5 -in test.nex -out test50.nex");
+ }else if(cmd.compareTo("concat") == 0){
+ System.out.println("concatenate alignments together");
+ System.out.println("options:");
+ System.out.println(" -concat | designates that you want to concatenate");
+ System.out.println(" -in <file name> ... | input file names");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println(" -aa | force amino acid files");
+ System.out.println("java -jar phyutility.jar -concat -in test.aln test2.aln -out testall.aln");
+ }else if(cmd.compareTo("ncbiget") == 0){
+ System.out.println("fetch sequences from ncbi");
+ System.out.println("options:");
+ System.out.println(" -ef | designates that you want to perform a fetch");
+ System.out.println(" -term <term> | the search term(s)");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println(" -ll <length> | the length limit to return, meant to eliminate genomic sequences or sequences that are too long in the retrieval (should almost always use, especially use if you get a memory error) (optional,default = 10000)");
+ System.out.println(" -outfor <format> | a way to customize the fasta line (1 = ginumber, 2 = taxid, 3 = orgname (with spaces replaced with whatever is sep), 4 = defline (with spaces replaced with sep), 5 = seqlength)(optional, default = 31)");
+ System.out.println(" -sep <seperator> | seperator for between outfor and between orgname and de
ine (optional, default= _)");
+ System.out.println(" -db # | corresponds to the database you want to search (nucleotide (1), protein (2)) (optional, nucleotide is default)");
+ System.out.println(" java -jar phyutility.jar -ef -term lonicera+OR+viburnum+AND+rbcl -ll 3000 -log log.txt -out out.fasta -sep -outfor 13");
+ }else if(cmd.compareTo("ncbisearch") == 0){
+ System.out.println("found out how many sequences match a query -- probably before ncbifetch -- log file records the GI numbers");
+ System.out.println("options:");
+ System.out.println(" -es | designates that you want to perform a search");
+ System.out.println(" -term <term> | the search term(s)");
+ System.out.println(" -db # | corresponds to the database you want to search (right now nucleotide (1), protein (2), genome (3), taxonomy (4)) (optional, default = nucleotide)");
+ System.out.println(" -log <file name> | log file name");
+ System.out.println("java -jar phyutility.jar -es -term lonicera+OR+viburnum+AND+rbcl -log log.txt");
+ }else if(cmd.compareTo("parse") == 0){
+ System.out.println("parses fasta files downloaded from genbank");
+ System.out.println("options:");
+ System.out.println(" -parse # | designates that you want to parse and the option number must follow -- see documentation");
+ System.out.println(" -in <file name> ... | input fasta files");
+ System.out.println(" -out <file name> | output file name");
+ System.out.println("java -jar phyutility.jar -parse 1 -in test.gb -out testgb1.fasta");
+ }else{
+ System.out.println("don't recognize command: "+cmd);
+ printUsage();
+ System.exit(0);
+ }
+ }
+
+ public static void main(String [] args){
+ Main m = new Main(args);
+ }
+}
diff --git a/src/phyutility/mainrunner/TestFuncs.java b/src/phyutility/mainrunner/TestFuncs.java
new file mode 100644
index 0000000..9f7b900
--- /dev/null
+++ b/src/phyutility/mainrunner/TestFuncs.java
@@ -0,0 +1,1497 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package phyutility.mainrunner;
+
+
+import jade.tree.TreeReader;
+
+import java.util.*;
+import java.io.*;
+
+import jebl.evolution.io.ImportException;
+import phyutility.concat.Concat;
+import phyutility.drb.WwdEmbedded;
+
+/**
+ *
+ * @author smitty
+ */
+
+public class TestFuncs {
+ /*
+ * NEW
+ */
+ private boolean monophylyMask = false;
+
+ private boolean ambigdups = false;
+
+ private boolean paraortho = false;
+
+ private boolean paraorthoprune = false;
+
+
+ /*
+ * END NEW
+ */
+ //db
+ private WwdEmbedded db;
+
+ private boolean out_oth = false; //-othout
+ private boolean treesupp = false; //-ts
+ private boolean prune = false; //use -names for which to prune
+ private String tree = null; //-tree
+ private boolean consensus = false; //-con requires threshold
+ private double threshold = 0.0; //-t
+ private boolean reroot = false; //-rr
+ private boolean derb = false;
+ //sequence functions
+ private boolean concat = false;
+ private boolean clean = false;
+ private double cleannum = 0.5;
+ private boolean parse = false;
+ private int parsenum = 1;
+
+ private ArrayList<String> mrcanames; //-names
+ private String logfile = null; //-log
+ private String outfile = null; //-out
+ private ArrayList<String> infiles; //-in
+ private FileWriter fw;
+
+ public TestFuncs(String [] args){
+ System.out.println("YOU HAVE ENTERED A PLACE YOU MAY WANT TO LEAVE");
+ processArgs(args);
+ runArgs();
+ }
+
+ private void processArgs(String [] args){
+ startLogging();
+ for(int i=0;i<args.length;i++){
+ if(args[i].toLowerCase().compareTo("-mm")==0){
+ monophylyMask = true;
+ log("monophyly movement\n");
+ }else if(args[i].toLowerCase().compareTo("-po")==0){
+ paraortho = true;
+ log("paralog ortholog\n");
+ }else if(args[i].toLowerCase().compareTo("-ad")==0){
+ this.ambigdups = true;
+ log("ambiguous duplicates\n");
+ }else if(args[i].toLowerCase().compareTo("-popr")==0){
+ this.paraorthoprune = true;
+ log("paralog ortholog pruning (the new one)\n");
+ }else if(args[i].toLowerCase().compareTo("-ts")==0){
+ treesupp = true;
+ log("tree support\n");
+ }else if(args[i].toLowerCase().compareTo("-pr")==0){
+ prune = true;
+ }else if(args[i].toLowerCase().compareTo("-t")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a threshhold after -t");
+ System.exit(0);
+ }else{
+ threshold = Double.valueOf(args[i]);
+ }
+ }else if(args[i].toLowerCase().compareTo("-names")==0){
+ mrcanames = new ArrayList<String>();
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter taxon names after -names");
+ System.exit(0);
+ }else{
+ while(args[i].trim().startsWith("-")==false){
+ mrcanames.add(args[i]);
+ log("mrca: "+args[i]+"\n");
+ i++;
+ if(i >= args.length){
+ break;
+ }
+ }
+ i--;
+ }
+ }
+ //seqence
+ else if(args[i].toLowerCase().compareTo("-concat")==0){
+ concat = true;
+ log("concat\n");
+ }else if(args[i].toLowerCase().compareTo("-clean")==0){
+ clean = true;
+ log("clean\n");
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a cleannum after -clean");
+ System.exit(0);
+ }else{
+ cleannum = Double.valueOf(args[i]);
+ log("cleannum: "+cleannum+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-parse")==0){
+ parse = true;
+ log("parse\n");
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a parsenum after -parse");
+ System.exit(0);
+ }else{
+ parsenum = Integer.valueOf(args[i]);
+ log("parsenum: "+parsenum+"\n");
+ }
+ }
+ //other
+ else if(args[i].toLowerCase().compareTo("-out")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a out filename after -out");
+ System.exit(0);
+ }else{
+ outfile = args[i];
+ log("outfile: "+outfile+"\n");
+ }
+ }else if(args[i].toLowerCase().compareTo("-in")==0){
+ infiles = new ArrayList<String>();
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter filenames after -in");
+ System.exit(0);
+ }else{
+ while(args[i].trim().startsWith("-")==false){
+ infiles.add(args[i]);
+ log("in filename: "+args[i]+"\n");
+ i++;
+ if(i >= args.length){
+ break;
+ }
+ }
+ i--;
+ }
+ }else if(args[i].toLowerCase().compareTo("-log")==0){
+ i++;
+ if(args[i].charAt(0)=='-'){
+ System.err.println("you have to enter a out filename after -log");
+ System.exit(0);
+ }
+ }else{
+ System.out.println("don't recognize argument "+args[i]);
+ }
+ }
+ }
+
+ private void runArgs(){
+ int x = 0;
+ if(treesupp == true){
+ x++;
+ }if(prune == true){
+ x++;
+ }if(monophylyMask == true){
+ x++;
+ }if(this.paraortho == true){
+ x++;
+ }if(this.ambigdups == true){
+ x++;
+ }if(this.paraorthoprune == true){
+ x++;
+ }
+ //sequence
+ if(concat == true){
+ x++;
+ }if(parse == true){
+ x++;
+ }if(clean == true){
+ x++;
+ }if(x == 0){
+ System.out.println("you have to enter some sort of analysis");
+ printUsage();
+ System.exit(0);
+ }if(x > 1){
+ System.out.println("you can only do one thing at a time");
+ printUsage();
+ System.exit(0);
+ }
+ if(infiles == null){
+ System.out.println("you have to enter some infiles (-in)");
+ printUsage();
+ System.exit(0);
+ }
+ /*
+ * NEW
+ */
+ if(this.monophylyMask == true){
+ this.monophylyMasking();
+ System.exit(0);
+ }if(this.paraortho == true){
+ this.paralog();
+ System.exit(0);
+ }if(this.ambigdups == true){
+ this.ambigdup();
+ }if(this.paraorthoprune == true){
+ this.paraorthoprune();
+ }
+ /*
+ * END NEW
+ */
+ if(consensus == true){
+ consensus();
+ System.exit(0);
+ }if(reroot == true){
+ reroot();
+ System.exit(0);
+ }if(prune == true){
+ prune();
+ System.exit(0);
+ }
+ //sequence
+ if(concat == true){
+ concat();
+ System.exit(0);
+ }
+ }
+
+ private void monophylyMasking(){
+ if(outfile == null){
+ System.out.println("please put an out file (-out)");
+ System.exit(0);
+ }
+ /*
+ * masking
+ *
+ * BASICALLY
+ * move from the tips to the root and detect monophyly of things with
+ * the first two parts of the name identical.
+ * DON'T cross the root.
+ *
+ * EASIEST way is to just check each taxa name
+ */
+
+ /*
+ * get all the trees
+ */
+ ArrayList<jade.tree.Tree> intrees = simpleReadJADETrees();
+ /*
+ * go through each tree and check for monophyly each taxa (and mask
+ * where found)
+ */
+ for(int i=0;i<intrees.size();i++){
+ //System.out.println("==");
+ //make an arraylist for each and remove as it goes
+ ArrayList<jade.tree.Node> toprune = new ArrayList<jade.tree.Node>();
+ boolean keep = true;
+ while (keep == true) {
+ for (int j = 0; j < intrees.get(i).getInternalNodeCount(); j++) {
+ ArrayList<ArrayList<jade.tree.Node>> allChNdArr =
+ new ArrayList<ArrayList<jade.tree.Node>>();
+ ArrayList<jade.tree.Node> treeExtNodes =
+ this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ jade.tree.Node innode = intrees.get(i).getInternalNode(j);
+ ArrayList<jade.tree.Node> left = this.getDifferenceBetweenArray2(treeExtNodes,
+ this.getExtNodesFromIntNode(innode));
+ /*
+ * get arralists for all the children from the internal node
+ */
+ allChNdArr.add(left);
+ for (int k = 0; k < innode.getChildCount(); k++) {
+ allChNdArr.add(this.getExtNodesFromIntNode(innode.getChild(k)));
+ }
+ ArrayList<ArrayList<jade.tree.Node>> erase = new ArrayList<ArrayList<jade.tree.Node>>();
+ for (int k = 0; k < allChNdArr.size(); k++) {
+ if (allChNdArr.get(k).size() == 0 || this.allNamesSame(allChNdArr.get(k)) == false) {
+ erase.add(allChNdArr.get(k));
+ }
+ }
+ for (int k = 0; k < erase.size(); k++) {
+ allChNdArr.remove(erase.get(k));
+ }
+ //System.out.println("--"+allChNdArr.size());
+ Iterator it = allChNdArr.iterator();
+ keep = false;
+ while (it.hasNext()) {
+ ArrayList<jade.tree.Node> nds = (ArrayList<jade.tree.Node>) it.next();
+ erase = new ArrayList<ArrayList<jade.tree.Node>>();
+ for (int k = 0; k < allChNdArr.size(); k++) {
+ if (allChNdArr.get(k) != nds) {
+ if (nds.get(0).getName().split("@")[0].compareTo(
+ allChNdArr.get(k).get(0).getName().split("@")[0]) == 0) {
+ nds = this.combineLists(nds, allChNdArr.get(k));
+ erase.add(allChNdArr.get(k));
+ }
+ }
+ }
+ for (int k = 0; k < erase.size(); k++) {
+ allChNdArr.remove(erase.get(k));
+ }
+ /*
+ * remove all but one
+ */
+ for (int k = 1; k < nds.size(); k++) {
+ System.out.println(nds.get(k).getName());
+ if(intrees.get(i).getExternalNode(nds.get(k).getName()).getParent()==
+ intrees.get(i).getRoot()){
+ intrees.get(i).getRoot().removeChild(nds.get(k));
+ }else{
+ intrees.get(i).pruneExternalNode(nds.get(k));
+ }
+ j = 0;
+ keep = true;
+ }
+ }
+ }
+ }
+ try {
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ fw.write(intrees.get(i).getRoot().getNewick(true)+";\n");
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void ambigdup(){
+ if(outfile == null){
+ System.out.println("please put an out file (-out)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = simpleReadJADETrees();
+ for(int i=0;i<intrees.size();i++){
+ ArrayList<jade.tree.Node> allseqs = this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ int numOfNames = this.numOfDiffs(allseqs);
+ ArrayList<String> uniqNames = this.nameDiffs(allseqs);
+ int largest = 0;
+ for(int j=0;j<intrees.get(i).getInternalNodeCount();j++){
+ ArrayList<ArrayList<jade.tree.Node>> allChNdArr =
+ new ArrayList<ArrayList<jade.tree.Node>>();
+ ArrayList<jade.tree.Node> treeExtNodes =
+ this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ jade.tree.Node innode = intrees.get(i).getInternalNode(j);
+ ArrayList<jade.tree.Node> left = this.getDifferenceBetweenArray(treeExtNodes,
+ this.getExtNodesFromIntNode(innode));
+ /*
+ * get arralists for all the children from the internal node
+ */
+ if(innode != intrees.get(i).getRoot())
+ allChNdArr.add(left);
+ for(int k=0;k<innode.getChildCount();k++){
+ allChNdArr.add(this.getExtNodesFromIntNode(innode.getChild(k)));
+ }
+
+ //COMBINED LISTS
+ ArrayList<ArrayList<jade.tree.Node>> COMBINED = new ArrayList<ArrayList<jade.tree.Node>>();
+ int[][] y = jade.math.NChooseM.iterate_all_bv2small(allChNdArr.size());
+ for (int w = 0; w < y.length; w++) {
+ ArrayList<jade.tree.Node> TCOMBINED = new ArrayList<jade.tree.Node>();
+ for (int k = 0; k < y[w].length; k++) {
+ if(y[w][k] == 1){
+ TCOMBINED = this.combineLists(TCOMBINED, allChNdArr.get(k));
+ }
+ }
+ COMBINED.add(TCOMBINED);
+ }
+ for(int w = 0;w < COMBINED.size();w++){
+ if(COMBINED.get(w).size() == numOfNames &&
+ containsOnly(COMBINED.get(w),uniqNames) &&
+ containsAll(COMBINED.get(w), allseqs) &&
+ this.numOfDiffs(COMBINED.get(w))==numOfNames){
+ System.out.println(numOfNames +" "+COMBINED.get(w).size());
+
+ //for(int k=0;k<COMBINED.size();k++){
+ //System.out.println("=====");
+ ArrayList<jade.tree.Node> TCOMBINED = COMBINED.get(w);
+ for(int h=0;h<TCOMBINED.size();h++){
+ System.out.println(TCOMBINED.get(h).getName());
+ }
+ //}
+
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(intrees.get(i).getRoot().getNewick(true)+";");
+ jade.tree.Tree trt = tr.readTree();
+ ArrayList<jade.tree.Node> del = this.getDifferenceBetweenArray(this.getExtNodesFromIntNode(intrees.get(i).getRoot()),
+ COMBINED.get(w)
+ );
+ System.out.println(del.size());
+ for(int k=0;k<del.size();k++){
+ if(trt.getExternalNode(del.get(k).getName()).getParent()==
+ trt.getRoot()){
+ trt.getRoot().removeChild(trt.getExternalNode(del.get(k).getName()));
+ if(trt.getRoot().getChildCount()==1){
+ jade.tree.Node nr = trt.getRoot().getChild(0);
+ trt.setRoot(nr);
+ trt.processRoot();
+ }
+ System.out.println("YEAH"+trt.getRoot().getChildCount());
+ }else{
+ trt.pruneExternalNode(trt.getExternalNode(del.get(k).getName()));
+ }
+ System.out.println("--"+del.get(k).getName());
+ trt.processRoot();
+ }
+ try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outfile, true));
+ bw.write(trt.getRoot().getNewick(true) + ";\n");
+ bw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ for (int k = 0; k < COMBINED.get(w).size(); k++) {
+ this.log(COMBINED.get(w).get(k).getName()+"\n");
+ //allseqs.remove(COMBINED.get(w).get(k));
+ }
+ }
+
+ }
+
+ }
+ }
+ }
+
+ private void paralog(){
+ if(outfile == null){
+ System.out.println("please put an out file (-out)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = simpleReadJADETrees();
+ for(int i=0;i<intrees.size();i++){
+ ArrayList<jade.tree.Node> allseqs = this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ int numOfNames = this.numOfDiffs(allseqs);
+ ArrayList<String> uniqNames = this.nameDiffs(allseqs);
+ int largest = 0;
+ for(int j=0;j<intrees.get(i).getInternalNodeCount();j++){
+ ArrayList<ArrayList<jade.tree.Node>> allChNdArr =
+ new ArrayList<ArrayList<jade.tree.Node>>();
+ ArrayList<jade.tree.Node> treeExtNodes =
+ this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ jade.tree.Node innode = intrees.get(i).getInternalNode(j);
+ ArrayList<jade.tree.Node> left = this.getDifferenceBetweenArray(treeExtNodes,
+ this.getExtNodesFromIntNode(innode));
+ /*
+ * get arralists for all the children from the internal node
+ */
+ if(innode != intrees.get(i).getRoot())
+ allChNdArr.add(left);
+ for(int k=0;k<innode.getChildCount();k++){
+ allChNdArr.add(this.getExtNodesFromIntNode(innode.getChild(k)));
+ }
+
+ //COMBINED LISTS
+ ArrayList<ArrayList<jade.tree.Node>> COMBINED = new ArrayList<ArrayList<jade.tree.Node>>();
+ int[][] y = jade.math.NChooseM.iterate_all_bv2small(allChNdArr.size());
+ for (int w = 0; w < y.length; w++) {
+ ArrayList<jade.tree.Node> TCOMBINED = new ArrayList<jade.tree.Node>();
+ for (int k = 0; k < y[w].length; k++) {
+ if(y[w][k] == 1){
+ TCOMBINED = this.combineLists(TCOMBINED, allChNdArr.get(k));
+ }
+ }
+ COMBINED.add(TCOMBINED);
+ }
+ for(int w = 0;w < COMBINED.size();w++){
+ if(COMBINED.get(w).size() == numOfNames &&
+ containsOnly(COMBINED.get(w),uniqNames) &&
+ containsAll(COMBINED.get(w), allseqs) &&
+ this.numOfDiffs(COMBINED.get(w))==numOfNames){
+ System.out.println(numOfNames +" "+COMBINED.get(w).size());
+
+ //for(int k=0;k<COMBINED.size();k++){
+ //System.out.println("=====");
+ ArrayList<jade.tree.Node> TCOMBINED = COMBINED.get(w);
+ for(int h=0;h<TCOMBINED.size();h++){
+ System.out.println(TCOMBINED.get(h).getName());
+ }
+ //}
+
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(intrees.get(i).getRoot().getNewick(true)+";");
+ jade.tree.Tree trt = tr.readTree();
+ ArrayList<jade.tree.Node> del = this.getDifferenceBetweenArray(this.getExtNodesFromIntNode(intrees.get(i).getRoot()),
+ COMBINED.get(w)
+ );
+ System.out.println(del.size());
+ for(int k=0;k<del.size();k++){
+ if(trt.getExternalNode(del.get(k).getName()).getParent()==
+ trt.getRoot()){
+ trt.getRoot().removeChild(trt.getExternalNode(del.get(k).getName()));
+ if(trt.getRoot().getChildCount()==1){
+ jade.tree.Node nr = trt.getRoot().getChild(0);
+ trt.setRoot(nr);
+ trt.processRoot();
+ }
+ System.out.println("YEAH"+trt.getRoot().getChildCount());
+ }else{
+ trt.pruneExternalNode(trt.getExternalNode(del.get(k).getName()));
+ }
+ System.out.println("--"+del.get(k).getName());
+ trt.processRoot();
+ }
+ try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outfile, true));
+ bw.write(trt.getRoot().getNewick(true) + ";\n");
+ bw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ for (int k = 0; k < COMBINED.get(w).size(); k++) {
+ this.log(COMBINED.get(w).get(k).getName()+"\n");
+ allseqs.remove(COMBINED.get(w).get(k));
+ }
+ }
+
+ }
+
+ }
+ }
+
+ }
+
+ int U = 1;
+
+ private void paraorthoprune(){
+ if(outfile == null){
+ System.out.println("please put an out file (-out)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = simpleReadJADETrees();
+ for(int i=0;i<intrees.size();i++){
+ ArrayList<jade.tree.Tree> finaltrees = new ArrayList<jade.tree.Tree>();
+ //ArrayList<jade.tree.Node> allseqs = this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ //int numOfNames = this.numOfDiffs(allseqs);
+ //ArrayList<String> uniqNames = this.nameDiffs(allseqs);
+
+
+ boolean something = true; /* keep going while there are clades left
+ * in the tree
+ */
+ while (something == true) {
+ boolean gotone = false;
+ int largest = 0;
+ ArrayList<jade.tree.Node> largestAR = new ArrayList<jade.tree.Node>();
+
+ ArrayList<jade.tree.Node> allseqs = this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ int numOfNames = this.numOfDiffs(allseqs);
+ ArrayList<String> uniqNames = this.nameDiffs(allseqs);
+ finaltrees = new ArrayList<jade.tree.Tree>();
+ int LARGEST = 0;
+ for (int j = 0; j < intrees.get(i).getInternalNodeCount(); j++) {
+ ArrayList<ArrayList<jade.tree.Node>> allChNdArr =
+ new ArrayList<ArrayList<jade.tree.Node>>();
+ ArrayList<jade.tree.Node> treeExtNodes =
+ this.getExtNodesFromIntNode(intrees.get(i).getRoot());
+ jade.tree.Node innode = intrees.get(i).getInternalNode(j);
+ ArrayList<jade.tree.Node> left = this.getDifferenceBetweenArray(treeExtNodes,
+ this.getExtNodesFromIntNode(innode));
+ /*
+ * get arralists for all the children from the internal node
+ */
+ if (innode != intrees.get(i).getRoot()) {
+ allChNdArr.add(left);
+ }
+ for (int k = 0; k < innode.getChildCount(); k++) {
+ allChNdArr.add(this.getExtNodesFromIntNode(innode.getChild(k)));
+ }
+
+ //COMBINED LISTS
+ ArrayList<ArrayList<jade.tree.Node>> COMBINED = new ArrayList<ArrayList<jade.tree.Node>>();
+ int[][] y = jade.math.NChooseM.iterate_all_bv2small(allChNdArr.size());
+ for (int w = 0; w < y.length; w++) {
+ ArrayList<jade.tree.Node> TCOMBINED = new ArrayList<jade.tree.Node>();
+ for (int k = 0; k < y[w].length; k++) {
+ if (y[w][k] == 1) {
+ TCOMBINED = this.combineLists(TCOMBINED, allChNdArr.get(k));
+ }
+ }
+ COMBINED.add(TCOMBINED);
+ }
+ for (int w = 0; w < COMBINED.size(); w++) {
+ if (COMBINED.get(w).size() == numOfNames &&
+ containsOnly(COMBINED.get(w), uniqNames) &&
+ containsAll(COMBINED.get(w), allseqs) &&
+ this.numOfDiffs(COMBINED.get(w)) == numOfNames) {
+ System.out.println(numOfNames + " " + COMBINED.get(w).size());
+
+ //for(int k=0;k<COMBINED.size();k++){
+ //System.out.println("=====");
+ ArrayList<jade.tree.Node> TCOMBINED = COMBINED.get(w);
+ for (int h = 0; h < TCOMBINED.size(); h++) {
+ System.out.println(TCOMBINED.get(h).getName());
+ }
+ //}
+
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(intrees.get(i).getRoot().getNewick(true) + ";");
+ jade.tree.Tree trt = tr.readTree();
+ ArrayList<jade.tree.Node> del = this.getDifferenceBetweenArray(this.getExtNodesFromIntNode(intrees.get(i).getRoot()),
+ COMBINED.get(w));
+ System.out.println(del.size());
+ for (int k = 0; k < del.size(); k++) {
+ if (trt.getExternalNode(del.get(k).getName()).getParent() ==
+ trt.getRoot()) {
+ trt.getRoot().removeChild(trt.getExternalNode(del.get(k).getName()));
+ if (trt.getRoot().getChildCount() == 1) {
+ jade.tree.Node nr = trt.getRoot().getChild(0);
+ trt.setRoot(nr);
+ trt.processRoot();
+ }
+ System.out.println("YEAH" + trt.getRoot().getChildCount());
+ } else {
+ trt.pruneExternalNode(trt.getExternalNode(del.get(k).getName()));
+ }
+ System.out.println("--" + del.get(k).getName());
+ trt.processRoot();
+ }
+ /*try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outfile, true));
+ bw.write(trt.getRoot().getNewick(true) + ";\n");
+ bw.close();
+
+
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }*/
+ finaltrees.add(trt);
+ for (int k = 0; k < COMBINED.get(w).size(); k++) {
+ this.log(COMBINED.get(w).get(k).getName() + "\n");
+ //allseqs.remove(COMBINED.get(w).get(k));
+ }
+ gotone = true;
+ }else{//get the largest clade
+ if(this.allUnique(COMBINED.get(w)) &&
+ this.numOfDiffs(COMBINED.get(w))>LARGEST){
+ LARGEST = this.numOfDiffs(COMBINED.get(w));
+ largestAR = COMBINED.get(w);
+ }
+ }
+ }
+ }//end of internal node
+ if(U==1 && gotone == false){
+ System.out.println(LARGEST);
+ System.out.println(numOfNames + " " + largestAR.size());
+
+ //for(int k=0;k<COMBINED.size();k++){
+ //System.out.println("=====");
+ ArrayList<jade.tree.Node> TCOMBINED =largestAR;
+ for (int h = 0; h < TCOMBINED.size(); h++) {
+ System.out.println(TCOMBINED.get(h).getName());
+ }
+ //}
+
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ tr.setTree(intrees.get(i).getRoot().getNewick(true) + ";");
+ if(intrees.get(i).getExternalNodeCount()==1){
+ tr.setTree("("+intrees.get(i).getRoot().getNewick(true) + ");");
+ }
+ jade.tree.Tree trt = tr.readTree();
+ if (trt.getExternalNodeCount() > 1) {
+ ArrayList<jade.tree.Node> del = this.getDifferenceBetweenArray(this.getExtNodesFromIntNode(intrees.get(i).getRoot()),
+ largestAR);
+ System.out.println(del.size());
+ for (int k = 0; k < del.size(); k++) {
+ if (trt.getExternalNode(del.get(k).getName()).getParent() ==
+ trt.getRoot()) {
+ trt.getRoot().removeChild(trt.getExternalNode(del.get(k).getName()));
+ if (trt.getRoot().getChildCount() == 1) {
+ jade.tree.Node nr = trt.getRoot().getChild(0);
+ trt.setRoot(nr);
+ trt.processRoot();
+ }
+ System.out.println("YEAH" + trt.getRoot().getChildCount());
+ } else {
+ trt.pruneExternalNode(trt.getExternalNode(del.get(k).getName()));
+ }
+ System.out.println("--" + del.get(k).getName());
+ trt.processRoot();
+ }
+ }
+ /*try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outfile, true));
+ bw.write(trt.getRoot().getNewick(true) + ";\n");
+ bw.close();
+
+
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }*/
+ finaltrees.add(trt);
+ for (int k = 0; k < largestAR.size(); k++) {
+ this.log(largestAR.get(k).getName() + "\n");
+ //allseqs.remove(COMBINED.get(w).get(k));
+ }
+ }
+ if (finaltrees.size() == 0){
+ something = false;
+ }else{
+ /*
+ * put ambiguity code here
+ */
+ //get rid of identical trees
+ System.out.println("s"+finaltrees.size());
+ for(int z=0;z<finaltrees.size();z++){
+ for(int x=0;x<finaltrees.size();x++){
+ if(x != z){
+ if(this.getDifferenceBetweenArray2(
+ this.getExtNodesFromIntNode(finaltrees.get(x).getRoot()),
+ this.getExtNodesFromIntNode(finaltrees.get(z).getRoot())).size()
+ >0){
+ finaltrees.remove(x);
+ z = 0; x = 0;
+ }
+ }
+ }
+ }
+ //get rid of ambigious tips
+
+ System.out.println("e"+finaltrees.size());
+
+ /*
+ * get largest
+ */
+ int tlarge = 0;
+ int elarge = 0;
+ for(int z=0;z<finaltrees.size();z++){
+ if(finaltrees.get(z).getExternalNodeCount()>elarge){
+ elarge = finaltrees.get(z).getExternalNodeCount();
+ tlarge = z;
+ }
+ }
+ /*
+ * write to file
+ */
+ for(int x = 0; x < finaltrees.size();x++){
+ if(finaltrees.get(x).getExternalNodeCount() == elarge){
+ try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outfile, true));
+ bw.write(finaltrees.get(x).getRoot().getNewick(true) + ";\n");
+ bw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /*
+ * remove the largest one
+ */
+ for (int x = 0; x < finaltrees.size(); x++) {
+ if (finaltrees.get(x).getExternalNodeCount() == elarge) {
+ if (finaltrees.get(x).getExternalNodeCount() >=
+ intrees.get(i).getExternalNodeCount() ) {//had a -1 at end
+ something = false;
+ } else {
+ for (int z = 0; z < finaltrees.get(x).getExternalNodeCount(); z++) {
+ if (intrees.get(i).getExternalNode(finaltrees.get(x).getExternalNode(z).getName()).getParent() ==
+ intrees.get(i).getRoot()) {
+ intrees.get(i).getRoot().removeChild(intrees.get(i).getExternalNode(finaltrees.get(x).getExternalNode(z).getName()));
+ if (intrees.get(i).getRoot().getChildCount() == 1) {
+ jade.tree.Node nr = intrees.get(i).getRoot().getChild(0);
+ intrees.get(i).setRoot(nr);
+ intrees.get(i).processRoot();
+ }
+ System.out.println("YEAH" + intrees.get(i).getRoot().getChildCount());
+ } else {
+ intrees.get(i).pruneExternalNode(intrees.get(i).getExternalNode(finaltrees.get(x).getExternalNode(z).getName()));
+ }
+ }
+ }
+
+ }
+ }
+
+ }
+ }//end of while
+ }
+ }
+
+ private void consensus(){
+ ArrayList<jebl.evolution.trees.Tree> intrees = new ArrayList<jebl.evolution.trees.Tree>();
+ ArrayList<jebl.evolution.trees.Tree> alltrees = new ArrayList<jebl.evolution.trees.Tree>();
+ boolean rooted = false;
+ for(int i=0;i<infiles.size();i++){
+ if(testForNexus(infiles.get(0))){
+ jebl.evolution.io.NexusImporter ni;
+ try {
+ ni = new jebl.evolution.io.NexusImporter(new FileReader(infiles.get(0)));
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ if(getJadeTreeFromJeblTree(alltrees.get(0)).getRoot().getChildCount()>2){
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ rooted = true;
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ alltrees = null;
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ jebl.evolution.io.NewickImporter ni;
+ try {
+ ni = new jebl.evolution.io.NewickImporter(new FileReader(infiles.get(0)), true);
+ alltrees = (ArrayList<jebl.evolution.trees.Tree>)ni.importTrees();
+ if(getJadeTreeFromJeblTree(alltrees.get(0)).getRoot().getChildCount()>2){
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ rooted = true;
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ alltrees = null;
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ if(rooted == true){
+ jebl.evolution.trees.RootedTree [] tr = new jebl.evolution.trees.RootedTree[intrees.size()];
+ for(int i=0;i<tr.length;i++){
+ tr[i] = (jebl.evolution.trees.RootedTree)intrees.get(i);
+ }
+ intrees = null;
+ jebl.evolution.trees.GreedyRootedConsensusTreeBuilder con = new jebl.evolution.trees.GreedyRootedConsensusTreeBuilder(tr, threshold);
+ if(outfile == null){
+ log("place an outfile to get internal node values (-out)\n");
+ System.out.println(jebl.evolution.trees.Utils.toNewick(con.build()));
+ }else{
+ try {
+ if(out_oth == true){
+ File toutf = new File(outfile+"temp");
+ BufferedWriter fw = new BufferedWriter(new FileWriter(toutf));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ ArrayList<jade.tree.Tree> ttrees = getNewickFromNexus(outfile+"temp");
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<ttrees.size();i++){
+ outw.write(ttrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ toutf.delete();
+ }else{
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }else{
+ jebl.evolution.trees.Tree [] tr = new jebl.evolution.trees.Tree[intrees.size()];
+ for(int i=0;i<tr.length;i++){
+ tr[i] = intrees.get(i);
+ }
+ intrees = null;
+ jebl.evolution.trees.GreedyUnrootedConsensusTreeBuilder con = new jebl.evolution.trees.GreedyUnrootedConsensusTreeBuilder(tr, null, threshold);
+ if(outfile == null){
+ log("place an outfile to get internal node values (-out)\n");
+ System.out.println(jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)con.build()));
+ }else{
+ try {
+ if(out_oth == true){
+ File toutf = new File(outfile+"temp");
+ BufferedWriter fw = new BufferedWriter(new FileWriter(toutf));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ ArrayList<jade.tree.Tree> ttrees = getNewickFromNexus(outfile+"temp");
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<ttrees.size();i++){
+ outw.write(ttrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ toutf.delete();
+ }else{
+ BufferedWriter fw = new BufferedWriter(new FileWriter(outfile));
+ jebl.evolution.io.NexusExporter ne = new jebl.evolution.io.NexusExporter(fw);
+ ne.exportTree(con.build());
+ fw.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void reroot(){
+ if(mrcanames == null){
+ System.out.println("you not entered MRCA or tip names when performing a reroot analysis (-names), so phyutility will attempt an unroot");
+ //System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ WwdEmbedded dbtemp = getNewickFromNexusDerby(infiles.get(i));
+ for(int j=0;j<dbtemp.getTableTreeSize();j++){
+ db.addTree(dbtemp.getTree(j)+";");
+ }
+ }else{
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ }
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ if(derb == true){
+ if(str.length()>1)
+ db.addTree(tr.readTree().getRoot().getNewick(true)+";");
+ }
+ else
+ intrees.add(tr.readTree());
+ }
+ if(derb ==true){
+ System.out.println("read "+db.getTableTreeSize());
+ }
+ br.close();
+ }catch(IOException ioe){}
+ }
+ }
+ if(outfile == null){
+ for(int i=0;i<intrees.size();i++){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ System.out.println(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ log("finished reroot\n");
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && out_oth != true)||
+ (testnex == false && out_oth == true)){
+ if(derb == true){
+ if(mrcanames != null){
+ Utils.newickToNexusRerootDerby(outfile, db, mrcanames);
+ }else{
+ Utils.newickToNexusUnrootDerby(outfile, db);
+ }
+ this.deleteDatabaseFolder(new File("null"));
+ }else{
+ ArrayList<String > nwa = new ArrayList<String>();
+ for(int i=0;i<intrees.size();i++){
+ if(mrcanames != null){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ }else{
+ intrees.get(i).unRoot(intrees.get(i).getRoot());
+ }
+ nwa.add(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ Utils.newickToNexus(outfile, nwa);
+ }
+ }else{
+ if(derb == true){
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<db.getTableTreeSize();i++){
+ jade.tree.Tree x = db.getJadeTree(i);
+ if(mrcanames != null){
+ x.reRoot(x.getMRCA(mrcanames));
+ }else{
+ x.reRoot(x.getRoot());
+ }
+ outw.write(x.getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ this.deleteDatabaseFolder(new File("null"));
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ for(int i=0;i<intrees.size();i++){
+ if(mrcanames != null){
+ intrees.get(i).reRoot(intrees.get(i).getMRCA(mrcanames));
+ }else{
+ intrees.get(i).unRoot(intrees.get(i).getRoot());
+ }
+ outw.write(intrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ }
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private void prune(){
+ if(mrcanames == null){
+ System.out.println("you have to enter tip names when performing a pruning analysis (-names)");
+ System.exit(0);
+ }
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ intrees.add(tr.readTree());
+ }
+ br.close();
+ }catch(IOException ioe){};
+ }
+ }
+ if(outfile == null){
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ pr.go();
+ for(int i=0;i<intrees.size();i++){
+ System.out.println(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ log("finished pruning\n");
+ }else{
+ try{
+ //nexus out
+ if((testnex == true && out_oth != true)||
+ (testnex == false && out_oth == true)){
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ intrees = pr.go();
+ ArrayList<String > nwa = new ArrayList<String>();
+ for(int i=0;i<intrees.size();i++){
+ nwa.add(intrees.get(i).getRoot().getNewick(true)+";");
+ }
+ Utils.newickToNexus(outfile, nwa);
+ }else{
+ FileWriter outw = new FileWriter(outfile);
+ phyutility.pruner.Pruner pr = new phyutility.pruner.Pruner(intrees, mrcanames);
+ intrees = pr.go();
+ for(int i=0;i<intrees.size();i++){
+ outw.write(intrees.get(i).getRoot().getNewick(true)+";\n");
+ }
+ outw.close();
+ }
+ }catch(IOException ioe){};
+ }
+ }
+
+ private ArrayList<jade.tree.Tree> getNewickFromNexus(String filename){
+ log("reading in nexus trees\n");
+ return phyutility.jebl2jade.NexusToJade.getJadeFromJeblNexus(filename);
+ }
+
+ private WwdEmbedded getNewickFromNexusDerby(String filename){
+ log("reading in nexus trees to derby\n");
+ return phyutility.jebl2jade.NexusToJade.getJadeFromJeblNexusDerby(filename);
+ }
+
+ private jade.tree.Tree getJadeTreeFromJeblTree(jebl.evolution.trees.Tree intr){
+ jade.tree.Tree rettr = null;
+ String st = jebl.evolution.trees.Utils.toNewick((jebl.evolution.trees.RootedTree)intr);
+ TreeReader tr = new TreeReader();
+ BufferedReader br;
+ br = new BufferedReader (new StringReader(st));
+ String str = "";
+ try {
+ while((str = br.readLine())!=null){
+ tr.setTree(str+";");
+ rettr = tr.readTree();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return rettr;
+ }
+
+ private ArrayList<jade.tree.Tree> simpleReadJADETrees(){
+ ArrayList<jade.tree.Tree> intrees = new ArrayList<jade.tree.Tree>();
+ ArrayList<jade.tree.Tree> alltrees = new ArrayList<jade.tree.Tree>();
+ boolean testnex = false;
+ for(int i=0;i<infiles.size();i++){
+ testnex = testForNexus(infiles.get(i));
+ if(testnex){
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ WwdEmbedded dbtemp = getNewickFromNexusDerby(infiles.get(i));
+ for(int j=0;j<dbtemp.getTableTreeSize();j++){
+ db.addTree(dbtemp.getTree(j)+";");
+ }
+ }else{
+ alltrees = getNewickFromNexus(infiles.get(i));
+ for(int j=0;j<alltrees.size();j++){
+ intrees.add(alltrees.get(j));
+ }
+ }
+ }else{
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(infiles.get(i)));
+ String str = "";
+ jade.tree.TreeReader tr = new jade.tree.TreeReader();
+ if(derb == true){
+ db = new WwdEmbedded("readtree");
+ db.connectToDB();
+ System.out.println("table made = "+db.makeTable(true));
+ }
+ while((str = br.readLine())!=null){
+ tr.setTree(str);
+ if(derb == true){
+ if(str.length()>1)
+ db.addTree(tr.readTree().getRoot().getNewick(true)+";");
+ }
+ else
+ intrees.add(tr.readTree());
+ }
+ if(derb ==true){
+ System.out.println("read "+db.getTableTreeSize());
+ }
+ br.close();
+ }catch(IOException ioe){}
+ }
+ }
+ return intrees;
+ }
+
+ /*
+ * sequence tools
+ */
+
+ /*
+ * takes fasta or nexus input (aligned)
+ * will output fasta or nexus (aligned)
+ * assumes different files are different genes
+ */
+ private void concat(){
+ Concat cc = new Concat(infiles,"test");
+ if(outfile != null){
+ if(out_oth == true){
+ cc.printtofileFASTA(outfile);
+ }else{//nexus
+ cc.printtofileNEXUS(outfile);
+ }
+ }else{
+ cc.printtoscreenNEXUS();
+ }
+ }
+
+ /*
+ * utils
+ */
+ private boolean testForNexus(String filename){
+ boolean ret = false;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ ret = true;
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+
+ private boolean deleteDatabaseFolder(File dir){
+ if (dir.isDirectory()) {
+ String[] children = dir.list();
+ for (int i=0; i<children.length; i++) {
+ boolean success = deleteDatabaseFolder(new File(dir, children[i]));
+ if (!success) {
+ return false;
+ }
+ }
+ }
+
+ // The directory is now empty so delete it
+ return dir.delete();
+ }
+
+ private ArrayList <jade.tree.Node> getExtNodesFromIntNode(jade.tree.Node node){
+ ArrayList<jade.tree.Node> retnd = new ArrayList<jade.tree.Node>();
+ if(node.isExternal()){
+ retnd.add(node);
+ return retnd;
+ }
+
+ Stack<jade.tree.Node> tempnd = new Stack<jade.tree.Node>();
+ tempnd.add(node);
+ jade.tree.Node curn = null;
+ while(tempnd.isEmpty()==false){
+ curn = tempnd.pop();
+ if(curn.isInternal()){
+ for(int i=0;i<curn.getChildCount();i++){
+ tempnd.push(curn.getChild(i));
+ }
+ }else{
+ retnd.add(curn);
+ }
+ }
+
+ return retnd;
+ }
+
+ private ArrayList<jade.tree.Node> getDifferenceBetweenArray(ArrayList<jade.tree.Node> arr1,
+ ArrayList<jade.tree.Node> arr2){
+
+ ArrayList<jade.tree.Node> retnd = new ArrayList<jade.tree.Node>();
+ for(int i=0;i<arr1.size();i++){
+ boolean keep = true;
+ for(int j=0;j<arr2.size();j++){
+ if(arr1.get(i)==arr2.get(j)){
+ keep = false;
+ break;
+ }
+ }
+ if(keep == true){
+ retnd.add(arr1.get(i));
+ }
+ }
+ for(int i=0;i<arr2.size();i++){
+ boolean keep = true;
+ for(int j=0;j<arr1.size();j++){
+ if(arr2.get(i)==arr1.get(j)){
+ keep = false;
+ break;
+ }
+ }
+ if(keep == true && retnd.contains(arr2.get(i))==false){
+ retnd.add(arr2.get(i));
+ }
+ }
+ return retnd;
+ }
+
+ private ArrayList<jade.tree.Node> getDifferenceBetweenArray2(ArrayList<jade.tree.Node> arr1,
+ ArrayList<jade.tree.Node> arr2){
+ ArrayList<jade.tree.Node> retnd1 = new ArrayList<jade.tree.Node>();
+ retnd1.addAll(arr1);
+ retnd1.removeAll(arr2);
+ ArrayList<jade.tree.Node> retnd2 = new ArrayList<jade.tree.Node>();
+ retnd2.addAll(arr2);
+ retnd2.removeAll(arr1);
+ ArrayList<jade.tree.Node> retnd = new ArrayList<jade.tree.Node>();
+ retnd.addAll(retnd1);
+ retnd.addAll(retnd2);
+ return retnd;
+ }
+
+ private ArrayList<jade.tree.Node> combineLists(ArrayList<jade.tree.Node> arr1,
+ ArrayList<jade.tree.Node> arr2){
+ ArrayList<jade.tree.Node> retar = new ArrayList<jade.tree.Node>();
+ retar.addAll(arr1);
+ retar.addAll(arr2);
+ return retar;
+ }
+
+ private boolean allNamesSame(ArrayList<jade.tree.Node>nodes){
+ boolean same = true;
+ String nodeName = nodes.get(0).getName().split("@")[0];
+ for(int i=0;i<nodes.size();i++){
+ if(nodeName.compareTo(nodes.get(i).getName().split("@")[0])!=0){
+ same = false;
+ }
+ }
+ return same;
+ }
+
+ private ArrayList<jade.tree.Node> leaveOne(ArrayList<jade.tree.Node> arr){
+ ArrayList<String> names = new ArrayList<String> ();
+ for(int i=0;i<arr.size();i++){
+ if (containsName(arr.get(i).getName().split("@")[0],names)==false){
+ names.add(arr.get(i).getName().split("@")[0]);
+ }
+ }
+ for(int i=0;i<names.size();i++){
+ for(int j=0;j<arr.size();j++){
+ if(arr.get(j).getName().split("@")[0].compareTo(names.get(i))==0){
+ System.out.println(arr.get(j).getName());
+ arr.remove(j);
+ break;
+ }
+ }
+ }
+ return arr;
+ }
+
+ private boolean containsName(String name, ArrayList<String> arr){
+ boolean cont = false;
+ for(int i=0;i<arr.size();i++){
+ if(arr.get(i).compareTo(name)==0){
+ cont = true;
+ break;
+ }
+ }
+ return cont;
+ }
+
+ private boolean containsAll(ArrayList<jade.tree.Node>nodes,ArrayList<jade.tree.Node>source){
+ boolean same = true;
+ for(int i=0;i<nodes.size();i++){
+ if(source.contains(nodes.get(i))==false){
+ same = false;
+ return false;
+ }
+ }
+ return same;
+ }
+
+ private boolean containsOnly(ArrayList<jade.tree.Node>nodes,ArrayList<String>source){
+ if (nodes.size() != source.size()){
+ return false;
+ }
+ int num = 0;
+ for(int i=0;i<source.size();i++){
+ for(int j=0;j<nodes.size();j++){
+ if(nodes.get(j).getName().split("@")[0].compareTo(source.get(i))==0){
+ num++;
+ break;
+ }
+ }
+ }
+ if(num == nodes.size() && num == source.size())
+ return true;
+ else
+ return false;
+ }
+
+ private ArrayList<String> nameDiffs(ArrayList<jade.tree.Node>nodes){
+ ArrayList<String> diffs = new ArrayList<String>();
+ for(int i=0;i<nodes.size();i++){
+ boolean test = false;
+ for(int j=0;j<diffs.size();j++){
+ if(nodes.get(i).getName().split("@")[0].compareTo(
+ diffs.get(j))==0){
+ test = true;
+ }
+ }
+ if(test == false){
+ diffs.add(nodes.get(i).getName().split("@")[0]);
+ }
+ }
+ return diffs;
+ }
+
+ private int numOfDiffs(ArrayList<jade.tree.Node>nodes){
+ ArrayList<String> diffs = new ArrayList<String>();
+ for(int i=0;i<nodes.size();i++){
+ boolean test = false;
+ for(int j=0;j<diffs.size();j++){
+ if(nodes.get(i).getName().split("@")[0].compareTo(
+ diffs.get(j))==0){
+ test = true;
+ }
+ }
+ if(test == false){
+ diffs.add(nodes.get(i).getName().split("@")[0]);
+ }
+ }
+ return diffs.size();
+ }
+
+ private boolean allUnique(ArrayList<jade.tree.Node>nodes){
+ boolean ret = true;
+ ArrayList<String> diffs = this.nameDiffs(nodes);
+ return this.containsOnly(nodes, diffs);
+ }
+
+ private void startLogging(){
+ try {
+ if(logfile == null){
+ /*java.util.Date dt = new java.util.Date();
+ java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("dd.MM.yy.hh.mm");
+ String datenewformat = formatter.format(dt);
+ logfile = datenewformat+".txt";*/
+ logfile = "phyutility.log";
+ }
+ fw = new FileWriter(logfile,true);
+ java.util.Date dt = new java.util.Date();
+ java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("dd/MM/yy hh:mm");
+ String datenewformat = formatter.format(dt);
+ log("starting log: "+datenewformat+"\n");
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void log(String instring){
+ try {
+ fw.write(instring);
+ fw.flush();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void stopLogging(){
+ try {
+ fw.flush();
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void printUsage(){
+ System.out.println("Phyutility (fyoo-til-i-te) v.2.2.1");
+ System.out.println("Stephen A. Smith http://www.blackrim.org stephen.smith at yale.edu");
+ System.out.println("help on a specific command use option -h <command>");
+ System.out.println("commands:");
+ System.out.println("trees:");
+ System.out.println(" consensus");
+ System.out.println(" convert");
+ System.out.println(" leafstab");
+ System.out.println(" linmove");
+ System.out.println(" prune");
+ System.out.println(" reroot");
+ System.out.println(" thin");
+ System.out.println(" treesupp");
+ System.out.println("seqs:");
+ System.out.println(" clean");
+ System.out.println(" concat");
+ System.out.println(" ncbiget");
+ System.out.println(" ncbisearch");
+ System.out.println(" parse");
+ System.out.println("see documentation for more information");
+ }
+
+}
diff --git a/src/phyutility/mainrunner/Utils.java b/src/phyutility/mainrunner/Utils.java
new file mode 100644
index 0000000..9949777
--- /dev/null
+++ b/src/phyutility/mainrunner/Utils.java
@@ -0,0 +1,80 @@
+package phyutility.mainrunner;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import phyutility.drb.*;
+
+public class Utils {
+ public static void newickToNexus(String outfile, ArrayList<String> nw){
+ FileWriter outw;
+ try {
+ outw = new FileWriter(outfile);
+ outw.write("#NEXUS\n");
+ outw.write("BEGIN TREES;\n");
+ for(int i=0;i<nw.size();i++){
+ outw.write("\tTREE tree"+i+" = "+nw.get(i)+"\n");
+ }
+ outw.write("END;\n\n");
+ outw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static void newickToNexusDerby(String outfile, WwdEmbedded derb){
+ FileWriter outw;
+ try {
+ outw = new FileWriter(outfile);
+ outw.write("#NEXUS\n");
+ outw.write("BEGIN TREES;\n");
+ for(int i=0;i<derb.getTableTreeSize();i++){
+ jade.tree.Tree x = derb.getJadeTree(i);
+ outw.write("\tTREE tree"+i+" = "+x.getRoot().getNewick(true)+";"+"\n");
+ }
+ outw.write("END;\n\n");
+ outw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static void newickToNexusRerootDerby(String outfile, WwdEmbedded derb, ArrayList<String> names){
+ FileWriter outw;
+ try {
+ outw = new FileWriter(outfile);
+ outw.write("#NEXUS\n");
+ outw.write("BEGIN TREES;\n");
+ for(int i=0;i<derb.getTableTreeSize();i++){
+ jade.tree.Tree x = derb.getJadeTree(i);
+ x.reRoot(x.getMRCA(names));
+ outw.write("\tTREE tree"+i+" = "+x.getRoot().getNewick(true)+";"+"\n");
+ }
+ outw.write("END;\n\n");
+ outw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ public static void newickToNexusUnrootDerby(String outfile, WwdEmbedded derb){
+ FileWriter outw;
+ try {
+ outw = new FileWriter(outfile);
+ outw.write("#NEXUS\n");
+ outw.write("BEGIN TREES;\n");
+ for(int i=0;i<derb.getTableTreeSize();i++){
+ jade.tree.Tree x = derb.getJadeTree(i);
+ x.unRoot(x.getRoot());
+ outw.write("\tTREE tree"+i+" = "+x.getRoot().getNewick(true)+";"+"\n");
+ }
+ outw.write("END;\n\n");
+ outw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/phyutility/ncbi/EFetchContentHandler.java b/src/phyutility/ncbi/EFetchContentHandler.java
new file mode 100644
index 0000000..e76433e
--- /dev/null
+++ b/src/phyutility/ncbi/EFetchContentHandler.java
@@ -0,0 +1,168 @@
+package phyutility.ncbi;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class EFetchContentHandler extends DefaultHandler{
+ private Stack stack;
+ private boolean isStackReadyForText;
+ public String webenv = "";
+ public String querykey = "";
+ public String gi = "";
+ public String taxid = "";
+ public String orgname = "";
+ public String length = "";
+ public String defline = "";
+ public String sequence ="";
+ int lengthlim = 10000;
+ boolean dontread = false;
+ private String outfor = "31";
+ private String sep = "_";
+ private String outfile;
+ private FileWriter fw;
+ private ArrayList<String> allfors;
+
+ public EFetchContentHandler(Integer lengthlim, String outfile, String outfor, String sep){
+ stack = new Stack();
+ isStackReadyForText = false;
+ if(lengthlim != null)
+ this.lengthlim = lengthlim;
+ if(outfor != null)
+ this.outfor = outfor;
+ if(sep != null)
+ this.sep = sep;
+ if(outfile != null){
+ this.outfile = outfile;
+ try {
+ fw = new FileWriter(outfile);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void startElement(String namespaceURI, String localName,
+ String qName, Attributes atts) {
+ if (localName.equals("TSeq_gi")) {
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("TSeq_taxid")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("TSeq_orgname")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("TSeq_defline")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("TSeq_length")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("TSeq_sequence")&& dontread == false){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else{
+ stack.push(null);
+ }
+ }
+
+ public void endElement( String uri, String localName, String qName ) {
+
+ // recognized text is always content of an element
+ // when the element closes, no more text should be expected
+ isStackReadyForText = false;
+
+ // pop stack and add to 'parent' element, which is next on the stack
+ // important to pop stack first, then peek at top element!
+ Object tmp = stack.pop();
+
+ if( localName.equals( "TSeq_gi" ) ) {
+ gi = tmp.toString();
+ }else if(localName.equals("TSeq_taxid")){
+ taxid = tmp.toString();
+ }else if(localName.equals("TSeq_orgname")){
+ orgname = tmp.toString();
+ }else if(localName.equals("TSeq_defline")){
+ defline = tmp.toString();
+ }else if(localName.equals("TSeq_length")){
+ length = tmp.toString();
+ if(Integer.valueOf(length) > lengthlim)
+ dontread = true;
+ else
+ dontread = false;
+ //System.out.println(length);
+ }else if(localName.equals("TSeq_sequence")&& dontread == false){
+ if(dontread == false){
+ sequence = tmp.toString();
+ printall();
+ }
+ }
+ else{
+ stack.push( tmp );
+ }
+ }
+
+ private void printall(){
+ if(dontread == false){
+ allfors = new ArrayList<String>();
+ allfors.add(gi);allfors.add(taxid);allfors.add(orgname);allfors.add(defline);
+ allfors.add(length);allfors.add(sequence);
+ String pr = ">";
+ for(int i=0;i<outfor.length();i++){
+ String ac = allfors.get(Integer.valueOf(outfor.substring(i, i+1))-1);
+ pr = pr.concat(ac.replaceAll(" ", sep));
+ if(i+1 < outfor.length())
+ pr = pr.concat(sep);
+ }
+ pr = pr.concat("\n");
+ if(outfile == null){
+ System.out.print(pr);
+ System.out.println(sequence);
+ }else{
+ try {
+ fw.write(pr);
+ fw.write(sequence+"\n");
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+
+ }
+ }
+ }
+ }
+
+ public void close(){
+ if(outfile!=null){
+ try {
+ fw.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ // -----
+
+ public void characters( char[] data, int start, int length ) {
+
+ // if stack is not ready, data is not content of recognized element
+ if( isStackReadyForText == true ) {
+ ((StringBuffer)stack.peek()).append( data, start, length );
+ }else{
+ // read data which is not part of recognized element
+ }
+ }
+
+
+ private String resolveAttrib( String uri, String localName,
+ Attributes attribs, String defaultValue ) {
+
+ String tmp = attribs.getValue( uri, localName );
+ return (tmp!=null)?(tmp):(defaultValue);
+ }
+}
diff --git a/src/phyutility/ncbi/ESearchContentHandler.java b/src/phyutility/ncbi/ESearchContentHandler.java
new file mode 100644
index 0000000..f6e5dd0
--- /dev/null
+++ b/src/phyutility/ncbi/ESearchContentHandler.java
@@ -0,0 +1,76 @@
+package phyutility.ncbi;
+
+import java.util.*;
+import java.io.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+public class ESearchContentHandler extends DefaultHandler {
+ private Stack stack;
+ private boolean isStackReadyForText;
+ public String webenv = "";
+ public String querykey = "";
+ public ArrayList<String> ids = new ArrayList<String>();
+ public ESearchContentHandler(){
+ stack = new Stack();
+ isStackReadyForText = false;
+ }
+
+ public void startElement(String namespaceURI, String localName,
+ String qName, Attributes atts) {
+ if (localName.equals("Id")) {
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("WebEnv")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else if(localName.equals("QueryKey")){
+ stack.push( new StringBuffer() );
+ isStackReadyForText = true;
+ }else{
+ stack.push(null);
+ }
+ }
+
+ public void endElement( String uri, String localName, String qName ) {
+
+ // recognized text is always content of an element
+ // when the element closes, no more text should be expected
+ isStackReadyForText = false;
+
+ // pop stack and add to 'parent' element, which is next on the stack
+ // important to pop stack first, then peek at top element!
+ Object tmp = stack.pop();
+
+ if( localName.equals( "Id" ) ) {
+ ids.add(tmp.toString() );
+ }else if(localName.equals("WebEnv")){
+ webenv = tmp.toString();
+ }else if(localName.equals("QueryKey")){
+ querykey = tmp.toString();
+ }
+ else{
+ stack.push( tmp );
+ }
+ }
+
+ // -----
+
+ public void characters( char[] data, int start, int length ) {
+
+ // if stack is not ready, data is not content of recognized element
+ if( isStackReadyForText == true ) {
+ ((StringBuffer)stack.peek()).append( data, start, length );
+ }else{
+ // read data which is not part of recognized element
+ }
+ }
+
+
+ private String resolveAttrib( String uri, String localName,
+ Attributes attribs, String defaultValue ) {
+
+ String tmp = attribs.getValue( uri, localName );
+ return (tmp!=null)?(tmp):(defaultValue);
+ }
+}
diff --git a/src/phyutility/ncbi/Einfo.java b/src/phyutility/ncbi/Einfo.java
new file mode 100644
index 0000000..0b41e5b
--- /dev/null
+++ b/src/phyutility/ncbi/Einfo.java
@@ -0,0 +1,84 @@
+package phyutility.ncbi;
+
+import java.io.*;
+import java.net.*;
+import java.util.ArrayList;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+public class Einfo {
+ private static String esearchbase = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?";
+ private static String efetchbase = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?";
+ private String retmax = "10000000";
+ String webenv = "";
+ String querykey = "";
+
+ public Einfo(){
+
+ }
+
+ public ArrayList<String> search(String db, String term){
+ try{
+ URL yh = new URL(esearchbase+"db="+db+"&usehistory=y&retmax="+retmax+"&term="+term);
+ HttpURLConnection ut =(HttpURLConnection) yh.openConnection();
+ ut.setDoOutput(true);
+ BufferedReader in = new BufferedReader(new InputStreamReader(ut.getInputStream()));
+ //String inputLine;
+ //while ((inputLine = in.readLine()) != null)
+ // System.out.println(inputLine);
+
+ ESearchContentHandler handler = new ESearchContentHandler();
+ XMLReader reader = XMLReaderFactory.createXMLReader( );
+ InputSource inputSource = new InputSource(ut.getInputStream());
+ reader.setContentHandler(handler);
+ reader.parse(inputSource);
+ webenv = handler.webenv;
+ querykey = handler.querykey;
+ ArrayList<String> ids = handler.ids;
+ //System.out.println("Count: "+ids.size());
+ in.close();
+ return ids;
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void efetch(String db, String term, int lengthlim, String outfile, String outfor, String sep){
+ search(db, term);
+ try{
+ URL yh = new URL(efetchbase+"rettype=fasta&retmode=xml&retmax="+retmax+"&db="+db+"&query_key="+querykey+"&WebEnv="+webenv);
+ HttpURLConnection ut =(HttpURLConnection) yh.openConnection();
+ ut.setDoOutput(true);
+ BufferedReader in = new BufferedReader(new InputStreamReader(ut.getInputStream()));
+ //String inputLine;
+ //while ((inputLine = in.readLine()) != null)
+ // System.out.println(inputLine);
+ EFetchContentHandler handler = new EFetchContentHandler(lengthlim, outfile, outfor, sep);
+ XMLReader reader = XMLReaderFactory.createXMLReader( );
+ InputSource inputSource = new InputSource(ut.getInputStream());
+ reader.setContentHandler(handler);
+ reader.parse(inputSource);
+ String gi = handler.gi;
+ String taxid = handler.taxid;
+ String orgname = handler.orgname;
+ String defline = handler.defline;
+ int length = Integer.valueOf(handler.length);
+ String seq = handler.sequence;
+ handler.close();
+ in.close();
+
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ Einfo ei = new Einfo();
+ ei.efetch("nucleotide", "lonicera+internal", 2000, null, null, null);
+ }
+}
diff --git a/src/phyutility/pruner/Pruner.java b/src/phyutility/pruner/Pruner.java
new file mode 100644
index 0000000..4b5999c
--- /dev/null
+++ b/src/phyutility/pruner/Pruner.java
@@ -0,0 +1,49 @@
+package phyutility.pruner;
+import jade.tree.TreeReader;
+import java.util.*;
+public class Pruner {
+ private ArrayList<jade.tree.Tree> intrees;
+ private ArrayList<String>names;
+ public Pruner(ArrayList<jade.tree.Tree> intrees, ArrayList<String>names){
+ this.intrees = intrees;
+ this.names = names;
+ }
+ public ArrayList<jade.tree.Tree> go(){
+ for(int i=0;i<intrees.size();i++){
+ for(int j=0;j<names.size();j++){
+ if(intrees.get(i).getExternalNode(names.get(j)) == null){
+ System.out.println("name: "+names.get(j)+" not in tree. exiting....");
+ System.exit(0);
+ }
+ /*
+ * added for nodes whose parents are the root
+ * when that happens
+ * just get the nodes other than the one you are trying to root
+ */
+ if (intrees.get(i).getExternalNode(names.get(j)).getParent().isTheRoot()){
+ if(intrees.get(i).getRoot().getChildCount()>2){
+ intrees.get(i).getRoot().removeChild(intrees.get(i).getExternalNode(names.get(j)));
+ intrees.get(i).processRoot();
+ }else{
+ TreeReader tr = new TreeReader();
+ String ts = "";
+ for (int k = 0; k < intrees.get(i).getRoot().getChildCount(); k++) {
+ if (intrees.get(i).getRoot().getChild(k) != intrees.get(i).getExternalNode(names.get(j))) {
+ ts = intrees.get(i).getRoot().getChild(k).getNewick(true)+";";
+ }
+ }
+ tr.setTree(ts);
+ intrees.set(i, tr.readTree());
+ }
+ }
+ /*
+ * end added
+ */
+ else{
+ intrees.get(i).pruneExternalNode(intrees.get(i).getExternalNode(names.get(j)));
+ }
+ }
+ }
+ return intrees;
+ }
+}
diff --git a/src/phyutility/treesupport/Runner.java b/src/phyutility/treesupport/Runner.java
new file mode 100644
index 0000000..c43f510
--- /dev/null
+++ b/src/phyutility/treesupport/Runner.java
@@ -0,0 +1,124 @@
+package phyutility.treesupport;
+
+import java.util.*;
+
+import jade.tree.*;
+
+public class Runner {
+
+ private ArrayList<ArrayList<String>> fixedtreenames = new ArrayList<ArrayList<String>>();
+ private Tree fixedtree;
+ private HashMap<Node, Double> incounts = new HashMap<Node, Double>();
+ private ArrayList<Tree> intrees = new ArrayList<Tree> ();
+
+ public Runner(ArrayList<Tree> intrees, Tree fixedtree){
+ this.intrees = intrees;
+ this.fixedtree = fixedtree;
+ }
+
+ public Tree run(){
+ initializeRun();
+ for(int i=0;i<intrees.size();i++){
+ ArrayList<ArrayList<String>> treenames = new ArrayList<ArrayList<String>>();
+ for(int j=0;j<intrees.get(i).getInternalNodeCount();j++){
+ getAllTipNodesFromInternalNode(intrees.get(i).getInternalNode(j));
+ treenames.add(tempNs);
+ }
+ compareTrees(treenames);
+ }
+ for(int i=0;i<fixedtree.getInternalNodeCount();i++){
+ if(incounts.get(fixedtree.getInternalNode(i))!=null){
+ if(fixedtree.getInternalNode(i).getName().length() >= 1){
+ fixedtree.getInternalNode(i).setName(fixedtree.getInternalNode(i).getName()+"_"+(incounts.get(fixedtree.getInternalNode(i))/intrees.size()));
+ }else{
+ fixedtree.getInternalNode(i).setName(String.valueOf(incounts.get(fixedtree.getInternalNode(i))/intrees.size()));
+ }
+ }
+ }
+ return fixedtree;
+ }
+
+ private void initializeRun(){
+ for(int i=0;i<fixedtree.getInternalNodeCount();i++){
+ getAllTipNodesFromInternalNode(fixedtree.getInternalNode(i));
+ fixedtreenames.add(tempNs);
+ }
+ }
+
+ ArrayList<String> tempNs;
+ private void getAllTipNodesFromInternalNode(Node intree){
+ tempNs = new ArrayList<String>();
+ poGATNFIN(intree);
+ }
+
+ private void poGATNFIN(Node innode){
+ for(int i=0;i<innode.getChildCount();i++){
+ poGATNFIN(innode.getChild(i));
+ }
+ if(innode.isExternal()==true){
+ tempNs.add(innode.getName().trim());
+ }
+ }
+
+ private void compareTrees(ArrayList<ArrayList<String>> intreestrings){
+ for(int i=0;i<fixedtreenames.size();i++){
+ boolean test = false;
+ for(int j=0;j<intreestrings.size();j++){
+ if(intreestrings.get(j).size() != fixedtreenames.get(i).size()){
+ continue;
+ }else{
+ test = testNames(fixedtreenames.get(i),intreestrings.get(j));
+ //System.out.println(test);
+ //printNames(fixedtreenames.get(i), fixedtreenames.get(i).size()+"+");
+ //printNames(intreestrings.get(j), intreestrings.get(j).size()+"-");
+ if(test == true)
+ break;
+ }
+ }
+ if(test ==true){
+ //printNames(fixedtreenames.get(i), fixedtreenames.get(i).size()+"=");
+ Node a = fixedtree.getMRCA(fixedtreenames.get(i));
+ if(incounts.get(a) == null){
+ incounts.put(a, 1.0);
+ }else{
+ incounts.put(a, incounts.get(a)+1);
+ }
+ }
+ }
+ }
+
+ private boolean testNames(ArrayList<String> one, ArrayList<String> two){
+ boolean ret = false;
+ int count = 0;
+ for(int i=0;i<one.size();i++){
+ boolean match = false;
+ for(int j=0;j<two.size();j++){
+ if(one.get(i).compareTo(two.get(j))==0){
+ match = true;
+ break;
+ }
+ }
+ if(match == true){
+ count++;
+ }else{
+ count = 0;
+ break;
+ }
+ }
+
+ if(count == one.size())
+ ret = true;
+ return ret;
+ }
+
+ private void printNames(ArrayList<String> names, String add){
+ System.out.print(add+" ");
+ for(int i=0;i<names.size();i++){
+ System.out.print(names.get(i)+"\t");
+ }System.out.println();
+ }
+
+ public static void main(String [] args){
+
+ }
+}
diff --git a/src/phyutility/trimsites/TrimSites.java b/src/phyutility/trimsites/TrimSites.java
new file mode 100644
index 0000000..3f9dd10
--- /dev/null
+++ b/src/phyutility/trimsites/TrimSites.java
@@ -0,0 +1,208 @@
+package phyutility.trimsites;
+import java.io.*;
+import java.util.*;
+
+import jebl.evolution.alignments.*;
+import jebl.evolution.io.*;
+import jebl.evolution.sequences.Sequence;
+import jebl.evolution.sequences.SequenceType;
+import jebl.evolution.sequences.State;
+import jebl.evolution.taxa.Taxon;
+
+public class TrimSites {
+
+ private BasicAlignment origaln;
+ private BasicAlignment trimedalign;
+
+ public TrimSites(String filename, String seqtype){
+ /*
+ * test for nucleotide vs amino acid
+ */
+ SequenceType usetype = null;
+ if (seqtype.compareTo("test") == 0)
+ usetype = testForSeqType(filename);
+ else if(seqtype.compareTo("nucleotide") == 0)
+ usetype = SequenceType.NUCLEOTIDE;
+ else if(seqtype.compareTo("aa") == 0)
+ usetype = SequenceType.AMINO_ACID;
+ //System.out.println(usetype.toString());
+ if(testForNexus(filename) == false){
+ File file = new File(filename);
+ try {
+ FastaImporter fi = new FastaImporter(file,usetype);
+
+ List<Sequence> seqs = fi.importSequences();
+ origaln = new BasicAlignment(seqs);
+ System.out.println("origsitecount "+origaln.getSiteCount());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }else{
+ File file = new File(filename);
+ try {
+ FileReader fr = new FileReader(file);
+ NexusImporter fi = new NexusImporter(fr);
+ List<Sequence> seqs = fi.importSequences();
+ origaln = new BasicAlignment(seqs);
+ System.out.println("origsitecount "+origaln.getSiteCount());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (ImportException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public BasicAlignment trimAln(double perc){
+ ArrayList<Integer> sites = new ArrayList<Integer>();
+ for(int i=0;i<origaln.getSiteCount();i++){
+ double count = 0;
+ Iterator <Sequence> seqs = origaln.getSequences().iterator();
+ while(seqs.hasNext()){
+ boolean gap = seqs.next().getState(i).isGap();
+ if(gap == false){
+ count ++;
+ }
+ }
+ if(count/origaln.getSequences().size()>perc){
+ sites.add(i);
+ }
+ }
+ ResampledAlignment ra = new ResampledAlignment();
+ int [] iar = new int [sites.size()];
+ for(int i=0;i<iar.length;i++){
+ iar[i] = sites.get(i);
+ }
+ ra.init(origaln, iar);
+ trimedalign = new BasicAlignment (ra.getSequences());
+ return trimedalign;
+ }
+
+ /*
+ * do after the trimAln
+ */
+ public BasicAlignment trimAlnCleanMessy(double perc){
+ ArrayList<Integer> sites = new ArrayList<Integer>();
+ for(int i=0;i<trimedalign.getSiteCount();i++){
+ ArrayList<String> sitestates = new ArrayList<String> ();
+ Iterator <Sequence> seqs = trimedalign.getSequences().iterator();
+ while(seqs.hasNext()){
+ State tstate = seqs.next().getState(i);
+ if (sitestates.contains(tstate.getName()) == false){
+ sitestates.add(tstate.getName());
+ }
+ }
+ if((sitestates.size()/(double)(trimedalign.getSequences().size()))<(1-perc)){
+ sites.add(i);
+ }
+ }
+ System.out.println(sites.size());
+ ResampledAlignment ra = new ResampledAlignment();
+ int [] iar = new int [sites.size()];
+ for(int i=0;i<iar.length;i++){
+ iar[i] = sites.get(i);
+ }
+ ra.init(trimedalign, iar);
+ trimedalign = new BasicAlignment (ra.getSequences());
+ return trimedalign;
+ }
+
+ public void printNexusOutfile(String outfile){
+ try {
+ FileWriter pw = new FileWriter(outfile);
+ NexusExporter ne = new NexusExporter(pw);
+ ne.exportAlignment(trimedalign);
+ pw.flush();
+ pw.close();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public void printFastaOutfile(String outfile){
+ try {
+ FileWriter pw = new FileWriter(outfile);
+ FastaExporter ne = new FastaExporter(pw);
+ ne.exportSequences(trimedalign.getSequences());
+ pw.flush();
+ pw.close();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private boolean testForNexus(String filename){
+ boolean ret = false;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ if(str.toUpperCase().trim().compareTo("#NEXUS")==0)
+ ret = true;
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+
+ private SequenceType testForSeqType(String filename){
+ SequenceType ret = SequenceType.NUCLEOTIDE;
+ String str = "";
+ try{
+ BufferedReader br = new BufferedReader(new FileReader(filename));
+ str = br.readLine();
+ while(str.length()<5 || str.startsWith(">")==true)
+ str = br.readLine();
+ if(str.length()>5 && str.startsWith(">") ==false)
+ ret = jebl.evolution.sequences.Utils.guessSequenceType(str);
+ br.close();
+ }catch(IOException ioe){}
+ return ret;
+ }
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+
+ // TODO Auto-generated method stub
+ TrimSites ts = new TrimSites("/Users/smitty/programming/RELEASES/phyutility/examples/test.fasta","nucleotide");
+ BasicAlignment test = ts.trimAln(0.49);
+ System.out.println("trimed length = "+test.getSiteCount());
+ /*Iterator<Sequence> seqs = test.getSequences().iterator();
+ while(seqs.hasNext()){
+ System.out.println(seqs.next().getString());
+ }*/
+ try {
+ FileWriter pw = new FileWriter("/Users/smitty/Desktop/some2.nex");
+ NexusExporter ne = new NexusExporter(pw);
+ ne.exportAlignment(test);
+ pw.flush();
+ pw.close();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/phyutility.git
More information about the debian-med-commit
mailing list