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.util.ArrayList;
024import java.util.List;
025
026import org.biojava.bio.seq.io.ParseException;
027
028/**
029 * Parses Nexus distances blocks.
030 * 
031 * @author Richard Holland
032 * @author Tobias Thierer
033 * @author Jim Balhoff
034 * @since 1.6
035 */
036public class DistancesBlockParser extends NexusBlockParser.Abstract {
037
038        private boolean expectingDimension;
039
040        private boolean expectingNewTaxa;
041
042        private boolean expectingNTax;
043
044        private boolean expectingNTaxEquals;
045
046        private boolean expectingNTaxValue;
047
048        private boolean expectingNChar;
049
050        private boolean expectingNCharEquals;
051
052        private boolean expectingNCharValue;
053
054        private boolean expectingFormat;
055
056        private boolean expectingTaxLabel;
057
058        private boolean expectingTaxLabelValue;
059
060        private boolean expectingMatrix;
061
062        private boolean expectingTriangle;
063
064        private boolean expectingTriangleEquals;
065
066        private boolean expectingTriangleContent;
067
068        private boolean expectingDiagonal;
069
070        private boolean expectingMissing;
071
072        private boolean expectingMissingEquals;
073
074        private boolean expectingMissingContent;
075
076        private boolean expectingLabels;
077
078        private boolean expectingInterleave;
079
080        private boolean expectingMatrixKey;
081
082        private boolean expectingMatrixContent;
083
084        private String currentMatrixKey;
085
086        private String matrixFirstLineKey;
087
088        private List matrixSeenKeys = new ArrayList();
089
090        private String triangleType;
091
092        /**
093         * Delegates to NexusBlockParser.Abstract.
094         * 
095         * @param blockListener
096         *            the listener to send parse events to.
097         */
098        public DistancesBlockParser(DistancesBlockListener blockListener) {
099                super(blockListener);
100        }
101
102        public void resetStatus() {
103                this.expectingDimension = true;
104                this.expectingNewTaxa = false;
105                this.expectingNTax = false;
106                this.expectingNTaxEquals = false;
107                this.expectingNTaxValue = false;
108                this.expectingNChar = false;
109                this.expectingNCharEquals = false;
110                this.expectingNCharValue = false;
111                this.expectingFormat = true;
112                this.expectingDiagonal = false;
113                this.expectingTaxLabel = true;
114                this.expectingTaxLabelValue = false;
115                this.expectingMatrix = true;
116                this.expectingTriangle = false;
117                this.expectingTriangleEquals = false;
118                this.expectingTriangleContent = false;
119                this.expectingMissing = false;
120                this.expectingMissingEquals = false;
121                this.expectingMissingContent = false;
122                this.expectingLabels = false;
123                this.expectingInterleave = false;
124                this.expectingMatrixKey = false;
125                this.expectingMatrixContent = false;
126                this.currentMatrixKey = null;
127                this.matrixFirstLineKey = null;
128                this.triangleType = "LOWER";
129                this.matrixSeenKeys.clear();
130        }
131
132        public boolean wantsBracketsAndBraces() {
133                return this.expectingMatrixContent;
134        }
135
136        public void parseToken(String token) throws ParseException {
137                if (this.expectingMatrixContent
138                                && "\n".equals(token)) {
139                        // Special handling for new lines inside matrix data.
140                        this.expectingMatrixContent = false;
141                        this.expectingMatrixKey = true;
142                } else if (token.trim().length() == 0)
143                        return;
144                else if (this.expectingDimension
145                                && "DIMENSIONS".equalsIgnoreCase(token)) {
146                        this.expectingDimension = false;
147                        this.expectingNewTaxa = true;
148                        this.expectingNChar = true;
149                } else if (this.expectingNewTaxa && "NEWTAXA".equalsIgnoreCase(token)) {
150                        this.expectingNewTaxa = false;
151                        this.expectingNTax = true;
152                        this.expectingNChar = false;
153                } else if (this.expectingNTax && token.toUpperCase().startsWith("NTAX")) {
154                        this.expectingNTax = false;
155                        if (token.indexOf('=') >= 0) {
156                                final String[] parts = token.split("=");
157                                if (parts.length > 1) {
158                                        this.expectingNChar = true;
159                                        try {
160                                                ((DistancesBlockListener) this.getBlockListener())
161                                                                .setDimensionsNTax(Integer.parseInt(parts[1]));
162                                        } catch (NumberFormatException e) {
163                                                throw new ParseException("Invalid NTAX value: "
164                                                                + parts[1]);
165                                        }
166                                } else
167                                        this.expectingNTaxValue = true;
168                        } else
169                                this.expectingNTaxEquals = true;
170                } else if (this.expectingNTaxEquals && token.startsWith("=")) {
171                        this.expectingNTaxEquals = false;
172                        final String[] parts = token.split("=");
173                        if (parts.length > 1) {
174                                this.expectingNChar = true;
175                                try {
176                                        ((DistancesBlockListener) this.getBlockListener())
177                                                        .setDimensionsNTax(Integer.parseInt(parts[1]));
178                                } catch (NumberFormatException e) {
179                                        throw new ParseException("Invalid NTAX value: " + parts[1]);
180                                }
181                        } else
182                                this.expectingNTaxValue = true;
183                } else if (this.expectingNTaxValue) {
184                        this.expectingNTaxValue = false;
185                        try {
186                                ((DistancesBlockListener) this.getBlockListener())
187                                                .setDimensionsNTax(Integer.parseInt(token));
188                        } catch (NumberFormatException e) {
189                                throw new ParseException("Invalid NTAX value: " + token);
190                        }
191                        this.expectingNChar = true;
192                } else if (this.expectingNChar
193                                && token.toUpperCase().startsWith("NCHAR")) {
194                        this.expectingNChar = false;
195                        if (token.indexOf('=') >= 0) {
196                                final String[] parts = token.split("=");
197                                if (parts.length > 1) {
198                                        try {
199                                                ((DistancesBlockListener) this.getBlockListener())
200                                                                .setDimensionsNChar(Integer.parseInt(parts[1]));
201                                        } catch (NumberFormatException e) {
202                                                throw new ParseException("Invalid NCHAR value: "
203                                                                + parts[1]);
204                                        }
205                                } else
206                                        this.expectingNCharValue = true;
207                        } else
208                                this.expectingNCharEquals = true;
209                } else if (this.expectingNCharEquals && token.startsWith("=")) {
210                        this.expectingNCharEquals = false;
211                        final String[] parts = token.split("=");
212                        if (parts.length > 1) {
213                                try {
214                                        ((DistancesBlockListener) this.getBlockListener())
215                                                        .setDimensionsNChar(Integer.parseInt(parts[1]));
216                                } catch (NumberFormatException e) {
217                                        throw new ParseException("Invalid NCHAR value: " + parts[1]);
218                                }
219                        } else
220                                this.expectingNCharValue = true;
221                } else if (this.expectingNCharValue) {
222                        this.expectingNCharValue = false;
223                        try {
224                                ((DistancesBlockListener) this.getBlockListener())
225                                                .setDimensionsNChar(Integer.parseInt(token));
226                        } catch (NumberFormatException e) {
227                                throw new ParseException("Invalid NCHAR value: " + token);
228                        }
229                }
230
231                else if (this.expectingFormat && "FORMAT".equalsIgnoreCase(token)) {
232                        this.expectingFormat = false;
233                        this.expectingTriangle = true;
234                        this.expectingDiagonal = true;
235                        this.expectingMissing = true;
236                        this.expectingLabels = true;
237                        this.expectingInterleave = true;
238                }
239
240                else if (this.expectingTriangle
241                                && token.toUpperCase().startsWith("TRIANGLE")) {
242                        this.expectingTriangle = false;
243
244                        if (token.indexOf("=") >= 0) {
245                                final String[] parts = token.split("=");
246                                if (parts.length > 1) {
247                                        this.triangleType = parts[1];
248                                        ((DistancesBlockListener) this.getBlockListener())
249                                                        .setTriangle(parts[1]);
250                                } else
251                                        this.expectingTriangleContent = true;
252                        } else
253                                this.expectingTriangleEquals = true;
254                }
255
256                else if (this.expectingTriangleEquals && token.startsWith("=")) {
257                        this.expectingTriangleEquals = false;
258                        if (token.length() > 1) {
259                                token = token.substring(1);
260                                this.triangleType = token;
261                                ((DistancesBlockListener) this.getBlockListener())
262                                                .setTriangle(token);
263                        } else
264                                this.expectingTriangleContent = true;
265                }
266
267                else if (this.expectingTriangleContent) {
268                        this.triangleType = token;
269                        ((DistancesBlockListener) this.getBlockListener())
270                                        .setTriangle(token);
271                        this.expectingTriangleContent = false;
272                }
273
274                else if (this.expectingDiagonal && "DIAGONAL".equalsIgnoreCase(token)) {
275                        ((DistancesBlockListener) this.getBlockListener())
276                                        .setDiagonal(true);
277                        this.expectingDiagonal = false;
278                }
279
280                else if (this.expectingDiagonal && "NODIAGONAL".equalsIgnoreCase(token)) {
281                        ((DistancesBlockListener) this.getBlockListener())
282                                        .setDiagonal(false);
283                        this.expectingDiagonal = false;
284                }
285
286                else if (this.expectingLabels && "LABELS".equalsIgnoreCase(token)) {
287                        ((DistancesBlockListener) this.getBlockListener()).setLabels(true);
288                        this.expectingLabels = false;
289                }
290
291                else if (this.expectingLabels && "NOLABELS".equalsIgnoreCase(token)) {
292                        ((DistancesBlockListener) this.getBlockListener()).setLabels(false);
293                        this.expectingLabels = false;
294                }
295
296                else if (this.expectingMissing
297                                && token.toUpperCase().startsWith("MISSING")) {
298                        this.expectingMissing = false;
299
300                        if (token.indexOf("=") >= 0) {
301                                final String[] parts = token.split("=");
302                                if (parts.length > 1)
303                                        ((DistancesBlockListener) this.getBlockListener())
304                                                        .setMissing(parts[1]);
305                                else
306                                        this.expectingMissingContent = true;
307                        } else
308                                this.expectingMissingEquals = true;
309                }
310
311                else if (this.expectingMissingEquals && token.startsWith("=")) {
312                        this.expectingMissingEquals = false;
313                        if (token.length() > 1)
314                                ((DistancesBlockListener) this.getBlockListener())
315                                                .setMissing(token.substring(1));
316                        else
317                                this.expectingMissingContent = true;
318                }
319
320                else if (this.expectingMissingContent) {
321                        ((DistancesBlockListener) this.getBlockListener())
322                                        .setMissing(token);
323                        this.expectingMissingContent = false;
324                }
325
326                else if (this.expectingInterleave
327                                && "INTERLEAVE".equalsIgnoreCase(token)) {
328                        ((DistancesBlockListener) this.getBlockListener())
329                                        .setInterleaved(true);
330                        this.expectingInterleave = false;
331                }
332
333                else if (this.expectingTaxLabel && "TAXLABELS".equalsIgnoreCase(token)) {
334                        this.expectingFormat = false;
335                        this.expectingTriangle = false;
336                        this.expectingLabels = false;
337                        this.expectingDiagonal = false;
338                        this.expectingMissing = false;
339                        this.expectingInterleave = false;
340                        this.expectingTaxLabel = false;
341                        this.expectingTaxLabelValue = true;
342                }
343
344                else if (this.expectingMatrix && "MATRIX".equalsIgnoreCase(token)) {
345                        this.expectingFormat = false;
346                        this.expectingTriangle = false;
347                        this.expectingLabels = false;
348                        this.expectingDiagonal = false;
349                        this.expectingMissing = false;
350                        this.expectingInterleave = false;
351                        this.expectingTaxLabel = false;
352                        this.expectingTaxLabelValue = false;
353                        this.expectingMatrix = false;
354                        this.expectingMatrixKey = true;
355                }
356
357                else if (this.expectingTaxLabelValue)
358                        // Use untoken version to preserve spaces.
359                        ((DistancesBlockListener) this.getBlockListener())
360                                        .addTaxLabel(token);
361
362                else if (this.expectingMatrixKey) {
363                        this.currentMatrixKey = token;
364                        // Use untoken version to preserve spaces.
365                        ((DistancesBlockListener) this.getBlockListener())
366                                        .addMatrixEntry(token);
367                        this.expectingMatrixKey = false;
368                        this.expectingMatrixContent = true;
369                        // Update first line info and set up stack for entry.
370                        if (!this.matrixSeenKeys.contains(token)) {
371                                if (this.triangleType.equalsIgnoreCase("UPPER"))
372                                        for (int i = 0; i < this.matrixSeenKeys.size(); i++)
373                                                ((DistancesBlockListener) this.getBlockListener())
374                                                                .appendMatrixData(this.currentMatrixKey, null);
375                                this.matrixSeenKeys.add(token);
376                        }
377                        if (this.matrixFirstLineKey == null)
378                                this.matrixFirstLineKey = this.currentMatrixKey;
379                }
380
381                else if (this.expectingMatrixContent)
382                        ((DistancesBlockListener) this.getBlockListener())
383                                        .appendMatrixData(this.currentMatrixKey, token);
384
385                else
386                        throw new ParseException("Found unexpected token " + token
387                                        + " in DISTANCES block");
388        }
389}