001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojavax.bio.phylo.io.nexus;
022
023import java.io.IOException;
024import java.io.Writer;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.Iterator;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.biojava.bio.seq.io.ParseException;
034
035/**
036 * Represents Nexus distances blocks.
037 * 
038 * @author Richard Holland
039 * @author Tobias Thierer
040 * @author Jim Balhoff
041 * @since 1.6
042 */
043public class DistancesBlock extends NexusBlock.Abstract {
044
045        /**
046         * A constant representing the name of Distances blocks.
047         */
048        public static final String DISTANCES_BLOCK = "DISTANCES";
049
050        private int dimensionsNTax = 0;
051
052        private int dimensionsNChar = 0;
053
054        private String triangle = "LOWER";
055
056        private boolean diagonal = true;
057
058        private boolean labels = true;
059
060        private String missing = "?";
061
062        private boolean interleaved = false;
063
064        private List taxLabels = new ArrayList();
065
066        // values are lists, containing strings and nulls which are gaps
067        private Map matrix = new LinkedHashMap();
068
069        private List comments = new ArrayList();
070
071        /**
072         * Delegates to NexusBlock.Abstract constructor using
073         * DistancesBlock.DISTANCES_BLOCK as the name.
074         */
075        public DistancesBlock() {
076                super(DistancesBlock.DISTANCES_BLOCK);
077        }
078
079        /**
080         * Set the NTAX value.
081         * 
082         * @param dimensionsNTax
083         *            the NTAX value.
084         */
085        public void setDimensionsNTax(int dimensionsNTax) {
086                this.dimensionsNTax = dimensionsNTax;
087        }
088
089        /**
090         * Get the NTAX value.
091         * 
092         * @return the NTAX value.
093         */
094        public int getDimensionsNTax() {
095                return this.dimensionsNTax;
096        }
097
098        /**
099         * Set the NCHAR value.
100         * 
101         * @param dimensionsNChar
102         *            the NCHAR value.
103         */
104        public void setDimensionsNChar(int dimensionsNChar) {
105                this.dimensionsNChar = dimensionsNChar;
106        }
107
108        /**
109         * Get the NCHAR value.
110         * 
111         * @return the NCHAR value.
112         */
113        public int getDimensionsNChar() {
114                return this.dimensionsNChar;
115        }
116
117        public void setTriangle(final String triangle) {
118                this.triangle = triangle;
119        }
120
121        public void setDiagonal(final boolean diagonal) {
122                this.diagonal = diagonal;
123        }
124
125        public boolean isDiagonal() {
126                return this.diagonal;
127        }
128
129        public void setLabels(final boolean labels) {
130                this.labels = labels;
131        }
132
133        public boolean isLabels() {
134                return this.labels;
135        }
136
137        public void setMissing(final String missing) {
138                this.missing = missing;
139        }
140
141        public String getMissing() {
142                return this.missing;
143        }
144
145        public void setInterleaved(final boolean interleaved) {
146                this.interleaved = interleaved;
147        }
148
149        public boolean isInterleaved() {
150                return this.interleaved;
151        }
152
153        /**
154         * Add a TAXLABEL. If it already exists, or is a number that refers to an
155         * index position that already exists, an exception is thrown.
156         * 
157         * @param taxLabel
158         *            the label to add.
159         * @throws ParseException
160         *             if the label cannot be added.
161         */
162        public void addTaxLabel(final String taxLabel) throws ParseException {
163                if (this.taxLabels.contains(taxLabel))
164                        throw new ParseException("Duplicate taxa label: " + taxLabel);
165                else
166                        try {
167                                // Try it as a number to see if it refers to
168                                // position we already have.
169                                final int i = Integer.parseInt(taxLabel);
170                                if (i <= this.taxLabels.size() + 1)
171                                        throw new ParseException("Taxa label " + i
172                                                        + " refers to already extant taxa position");
173                        } catch (NumberFormatException e) {
174                                // It is not a number, so ignore.
175                        } catch (ParseException e) {
176                                // Throw it.
177                                throw e;
178                        }
179                this.taxLabels.add(taxLabel);
180        }
181
182        /**
183         * Removes the given TAXLABEL.
184         * 
185         * @param taxLabel
186         *            the label to remove.
187         */
188        public void removeTaxLabel(final String taxLabel) {
189                this.taxLabels.remove(taxLabel);
190        }
191
192        /**
193         * Checks to see if we contain the given TAXLABEL.
194         * 
195         * @param taxLabel
196         *            the label to check for.
197         * @return <tt>true</tt> if we already contain it.
198         */
199        public boolean containsTaxLabel(final String taxLabel) {
200                if (this.taxLabels.contains(taxLabel))
201                        return true;
202                else
203                        try {
204                                // Try it as a number to see if it refers to
205                                // position we already have.
206                                final int i = Integer.parseInt(taxLabel);
207                                if (i <= this.taxLabels.size() + 1)
208                                        return true;
209                        } catch (NumberFormatException e) {
210                                // It is not a number, so ignore.
211                        }
212                return false;
213        }
214
215        /**
216         * Get the TAXLABEL values added so far.
217         * 
218         * @return this labels so far.
219         */
220        public List getTaxLabels() {
221                return this.taxLabels;
222        }
223
224        public void addMatrixEntry(final String taxa) {
225                if (!this.matrix.containsKey(taxa))
226                        this.matrix.put(taxa, new ArrayList());
227        }
228
229        public void appendMatrixData(final String taxa, final Object data) {
230                ((List) this.matrix.get(taxa)).add(data);
231        }
232
233        public List getMatrixData(final String taxa) {
234                return (List) this.matrix.get(taxa);
235        }
236        
237        public Collection getMatrixLabels() {
238                return Collections.unmodifiableSet(this.matrix.keySet());
239        }
240
241        /**
242         * Adds a comment.
243         * 
244         * @param comment
245         *            the comment to add.
246         */
247        public void addComment(final NexusComment comment) {
248                this.comments.add(comment);
249        }
250
251        /**
252         * Removes a comment.
253         * 
254         * @param comment
255         *            the comment to remove.
256         */
257        public void removeComment(final NexusComment comment) {
258                this.comments.remove(comment);
259        }
260
261        /**
262         * Returns all comments.
263         * 
264         * @return all the selected comments.
265         */
266        public List getComments() {
267                return this.comments;
268        }
269
270        protected void writeBlockContents(Writer writer) throws IOException {
271                for (final Iterator i = this.comments.iterator(); i.hasNext();) {
272                        ((NexusComment) i.next()).writeObject(writer);
273                        writer.write(NexusFileFormat.NEW_LINE);
274                }
275                writer.write(" DIMENSIONS ");
276                if (!this.taxLabels.isEmpty())
277                        writer.write("NEWTAXA ");
278                if (this.dimensionsNTax > 0)
279                        writer.write("NTAX=" + this.dimensionsNTax + " ");
280                writer.write("NCHAR=" + this.dimensionsNChar + ";"
281                                + NexusFileFormat.NEW_LINE);
282
283                writer.write(" FORMAT TRIANGLE=");
284                this.writeToken(writer, this.triangle);
285                writer.write(this.diagonal ? " DIAGONAL" : " NODIAGONAL");
286                writer.write(this.labels ? " LABELS" : " NOLABELS");
287                writer.write(" MISSING=");
288                this.writeToken(writer, this.missing);
289                if (this.interleaved)
290                        writer.write(" INTERLEAVED");
291                writer.write(";" + NexusFileFormat.NEW_LINE);
292
293                if (this.taxLabels.size() > 0) {
294                        writer.write(" TAXLABELS");
295                        for (final Iterator i = this.taxLabels.iterator(); i.hasNext();) {
296                                writer.write(' ');
297                                this.writeToken(writer, (String) i.next());
298                        }
299                        writer.write(";" + NexusFileFormat.NEW_LINE);
300                }
301
302                // if statesformat=statespresent and items=1, bracket only multi values,
303                // otherwise bracket all values
304                // only space tokens if reallyUseTokens=true
305                writer.write(" MATRIX" + NexusFileFormat.NEW_LINE);
306                for (final Iterator i = this.matrix.entrySet().iterator(); i.hasNext();) {
307                        final Map.Entry entry = (Map.Entry) i.next();
308                        writer.write('\t');
309                        this.writeToken(writer, "" + entry.getKey());
310                        writer.write('\t');
311                        for (final Iterator j = ((List) entry.getValue()).iterator(); j
312                                        .hasNext();) {
313                                final Object obj = j.next();
314                                if (obj instanceof String)
315                                        this.writeToken(writer, (String) obj);
316                                if (j.hasNext())
317                                        writer.write('\t');
318                        }
319                        writer.write(NexusFileFormat.NEW_LINE);
320                }
321                writer.write(";" + NexusFileFormat.NEW_LINE);
322
323        }
324}