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; 032import java.util.Set; 033 034import org.biojava.bio.seq.io.ParseException; 035 036/** 037 * Represents Nexus characters blocks. 038 * 039 * @author Richard Holland 040 * @author Tobias Thierer 041 * @author Jim Balhoff 042 * @since 1.6 043 */ 044public class CharactersBlock extends NexusBlock.Abstract { 045 046 /** 047 * A constant representing the name of Characters blocks. 048 */ 049 public static final String CHARACTERS_BLOCK = "CHARACTERS"; 050 051 private int dimensionsNTax = 0; 052 053 private int dimensionsNChar = 0; 054 055 private String dataType = "STANDARD"; 056 057 private boolean respectCase = false; 058 059 private String missing = "?"; 060 061 private String gap; 062 063 private List symbols = new ArrayList(); 064 065 private Map equate = new LinkedHashMap(); // values are lists 066 067 private String matchChar; 068 069 private boolean labels = true; 070 071 private boolean transposed = false; 072 073 private boolean interleaved = false; 074 075 private List items = new ArrayList(); 076 077 private String statesFormat = "STATESPRESENT"; 078 079 private boolean tokens = false; 080 081 private int eliminateStart = 0; 082 083 private int eliminateEnd = 0; 084 085 private List taxLabels = new ArrayList(); 086 087 // map containing two-value arrays where second value is list 088 private Map charStateLabels = new LinkedHashMap(); 089 090 private List charLabels = new ArrayList(); 091 092 private Map stateLabels = new LinkedHashMap(); // values are lists 093 094 // values are lists, containing nested mix of strings and lists and sets 095 private Map matrix = new LinkedHashMap(); 096 097 private List comments = new ArrayList(); 098 099 /** 100 * Delegates to NexusBlock.Abstract constructor using 101 * CharactersBlock.CHARACTERS_BLOCK as the name. 102 */ 103 public CharactersBlock() { 104 this(CharactersBlock.CHARACTERS_BLOCK); 105 } 106 107 /** 108 * For the DATA block subclass. 109 * 110 * @param replacementLabel 111 * the different label to use. 112 */ 113 protected CharactersBlock(final String replacementLabel) { 114 super(replacementLabel); 115 } 116 117 /** 118 * Set the NTAX value. 119 * 120 * @param dimensionsNTax 121 * the NTAX value. 122 */ 123 public void setDimensionsNTax(int dimensionsNTax) { 124 this.dimensionsNTax = dimensionsNTax; 125 } 126 127 /** 128 * Get the NTAX value. 129 * 130 * @return the NTAX value. 131 */ 132 public int getDimensionsNTax() { 133 return this.dimensionsNTax; 134 } 135 136 /** 137 * Set the NCHAR value. 138 * 139 * @param dimensionsNChar 140 * the NCHAR value. 141 */ 142 public void setDimensionsNChar(int dimensionsNChar) { 143 this.dimensionsNChar = dimensionsNChar; 144 } 145 146 /** 147 * Get the NCHAR value. 148 * 149 * @return the NCHAR value. 150 */ 151 public int getDimensionsNChar() { 152 return this.dimensionsNChar; 153 } 154 155 public void setDataType(final String dataType) { 156 this.dataType = dataType; 157 } 158 159 public String getDataType() { 160 return this.dataType; 161 } 162 163 public void setRespectCase(final boolean respectCase) { 164 this.respectCase = respectCase; 165 } 166 167 public boolean isRespectCase() { 168 return this.respectCase; 169 } 170 171 public void setMissing(final String missing) { 172 this.missing = missing; 173 } 174 175 public String getMissing() { 176 return this.missing; 177 } 178 179 public void setGap(final String gap) { 180 this.gap = gap; 181 } 182 183 public String getGap() { 184 return this.gap; 185 } 186 187 public void addSymbol(final String symbol) { 188 if (!this.symbols.contains(symbol)) 189 this.symbols.add(symbol); 190 } 191 192 public void removeSymbol(final String symbol) { 193 this.symbols.remove(symbol); 194 } 195 196 public void removeAllSymbols() { 197 this.symbols.clear(); 198 } 199 200 public List getSymbols() { 201 return this.symbols; 202 } 203 204 public void addEquate(final String symbol, final List symbols) { 205 this.equate.put(symbol, symbols); 206 } 207 208 public void removeEquate(final String symbol) { 209 this.equate.remove(symbol); 210 } 211 212 public Map getEquates() { 213 return this.equate; 214 } 215 216 public void setMatchChar(final String matchChar) { 217 this.matchChar = matchChar; 218 } 219 220 public String getMatchChar() { 221 return this.matchChar; 222 } 223 224 public void setLabels(final boolean labels) { 225 this.labels = labels; 226 } 227 228 public boolean isLabels() { 229 return this.labels; 230 } 231 232 public void setTransposed(final boolean transposed) { 233 this.transposed = transposed; 234 } 235 236 public boolean isTransposed() { 237 return this.transposed; 238 } 239 240 public void setInterleaved(final boolean interleaved) { 241 this.interleaved = interleaved; 242 } 243 244 public boolean isInterleaved() { 245 return this.interleaved; 246 } 247 248 public void addItem(final String item) { 249 if (!this.items.contains(item)) 250 this.items.add(item); 251 } 252 253 public void removeItem(final String item) { 254 this.items.remove(item); 255 } 256 257 public void removeAllItems() { 258 this.items.clear(); 259 } 260 261 public List getItems() { 262 return this.items; 263 } 264 265 public void setStatesFormat(final String statesFormat) { 266 this.statesFormat = statesFormat; 267 } 268 269 public String getStatesFormat() { 270 return this.statesFormat; 271 } 272 273 public void setTokens(final boolean tokens) { 274 this.tokens = tokens; 275 } 276 277 public boolean isTokens() { 278 return this.tokens; 279 } 280 281 public void setEliminateStart(final int eliminateStart) { 282 this.eliminateStart = eliminateStart; 283 } 284 285 public int getEliminateStart() { 286 return this.eliminateStart; 287 } 288 289 public void setEliminateEnd(final int eliminateEnd) { 290 this.eliminateEnd = eliminateEnd; 291 } 292 293 public int getEliminateEnd() { 294 return this.eliminateEnd; 295 } 296 297 /** 298 * Add a TAXLABEL. If it already exists, or is a number that refers to an 299 * index position that already exists, an exception is thrown. 300 * 301 * @param taxLabel 302 * the label to add. 303 * @throws ParseException 304 * if the label cannot be added. 305 */ 306 public void addTaxLabel(final String taxLabel) throws ParseException { 307 if (this.taxLabels.contains(taxLabel)) 308 throw new ParseException("Duplicate taxa label: " + taxLabel); 309 else 310 try { 311 // Try it as a number to see if it refers to 312 // position we already have. 313 final int i = Integer.parseInt(taxLabel); 314 if (i <= this.taxLabels.size() + 1) 315 throw new ParseException("Taxa label " + i 316 + " refers to already extant taxa position"); 317 } catch (NumberFormatException e) { 318 // It is not a number, so ignore. 319 } catch (ParseException e) { 320 // Throw it. 321 throw e; 322 } 323 this.taxLabels.add(taxLabel); 324 } 325 326 /** 327 * Removes the given TAXLABEL. 328 * 329 * @param taxLabel 330 * the label to remove. 331 */ 332 public void removeTaxLabel(final String taxLabel) { 333 this.taxLabels.remove(taxLabel); 334 } 335 336 /** 337 * Checks to see if we contain the given TAXLABEL. 338 * 339 * @param taxLabel 340 * the label to check for. 341 * @return <tt>true</tt> if we already contain it. 342 */ 343 public boolean containsTaxLabel(final String taxLabel) { 344 if (this.taxLabels.contains(taxLabel)) 345 return true; 346 else 347 try { 348 // Try it as a number to see if it refers to 349 // position we already have. 350 final int i = Integer.parseInt(taxLabel); 351 if (i <= this.taxLabels.size() + 1) 352 return true; 353 } catch (NumberFormatException e) { 354 // It is not a number, so ignore. 355 } 356 return false; 357 } 358 359 /** 360 * Get the TAXLABEL values added so far. 361 * 362 * @return this labels so far. 363 */ 364 public List getTaxLabels() { 365 return this.taxLabels; 366 } 367 368 public void addCharState(final String charState) { 369 this.charStateLabels.put(charState, new Object[] { null, 370 new ArrayList() }); 371 } 372 373 public void setCharStateLabel(final String charState, final String label) { 374 if (!this.charStateLabels.containsKey(charState)) 375 this.addCharState(charState); 376 ((Object[]) this.charStateLabels.get(charState))[0] = label; 377 } 378 379 public void addCharStateKeyword(final String charState, final String keyword) { 380 if (!this.charStateLabels.containsKey(charState)) 381 this.addCharState(charState); 382 ((List) ((Object[]) this.charStateLabels.get(charState))[1]) 383 .add(keyword); 384 } 385 386 public String getCharStateLabel(final String charState) { 387 return (String) (((Object[]) this.charStateLabels.get(charState))[0]); 388 } 389 390 public List getCharStateLabelKeywords(final String charState) { 391 return (List) (((Object[]) this.charStateLabels.get(charState))[1]); 392 } 393 394 public void removeCharState(final String charState) { 395 this.charStateLabels.remove(charState); 396 } 397 398 public Set getAllCharStates() { 399 return this.charStateLabels.keySet(); 400 } 401 402 public void addCharLabel(final String charLabel) { 403 this.charLabels.add(charLabel); 404 } 405 406 public void removeCharLabel(final String charLabel) { 407 this.charLabels.remove(charLabel); 408 } 409 410 public boolean containsCharLabel(final String charLabel) { 411 return this.charLabels.contains(charLabel); 412 } 413 414 public List getCharLabels() { 415 return this.charLabels; 416 } 417 418 public void addState(final String state) { 419 this.stateLabels.put(state, new ArrayList()); 420 } 421 422 public void addStateLabel(final String state, final String label) { 423 if (!this.stateLabels.containsKey(state)) 424 this.addState(state); 425 ((List) this.stateLabels.get(state)).add(label); 426 } 427 428 public List getStateLabels(final String state) { 429 return (List) this.stateLabels.get(state); 430 } 431 432 public void removeState(final String state) { 433 this.stateLabels.remove(state); 434 } 435 436 public void addMatrixEntry(final String taxa) { 437 if (!this.matrix.containsKey(taxa)) 438 this.matrix.put(taxa, new ArrayList()); 439 } 440 441 public void appendMatrixData(final String taxa, final Object data) { 442 ((List) this.matrix.get(taxa)).add(data); 443 } 444 445 public List getMatrixData(final String taxa) { 446 return (List) this.matrix.get(taxa); 447 } 448 449 public Collection getMatrixLabels() { 450 return Collections.unmodifiableSet(this.matrix.keySet()); 451 } 452 453 /** 454 * Adds a comment. 455 * 456 * @param comment 457 * the comment to add. 458 */ 459 public void addComment(final NexusComment comment) { 460 this.comments.add(comment); 461 } 462 463 /** 464 * Removes a comment. 465 * 466 * @param comment 467 * the comment to remove. 468 */ 469 public void removeComment(final NexusComment comment) { 470 this.comments.remove(comment); 471 } 472 473 /** 474 * Returns all comments. 475 * 476 * @return all the selected comments. 477 */ 478 public List getComments() { 479 return this.comments; 480 } 481 482 protected void writeBlockContents(Writer writer) throws IOException { 483 for (final Iterator i = this.comments.iterator(); i.hasNext();) { 484 ((NexusComment) i.next()).writeObject(writer); 485 writer.write(NexusFileFormat.NEW_LINE); 486 } 487 writer.write(" DIMENSIONS "); 488 if (!this.taxLabels.isEmpty()) 489 writer.write("NEWTAXA "); 490 if (this.dimensionsNTax > 0) 491 writer.write("NTAX=" + this.dimensionsNTax + " "); 492 writer.write("NCHAR=" + this.dimensionsNChar + ";" 493 + NexusFileFormat.NEW_LINE); 494 495 writer.write(" FORMAT DATATYPE="); 496 this.writeToken(writer, this.dataType); 497 if (this.respectCase && "STANDARD".equals(this.dataType)) 498 writer.write(" RESPECTCASE"); 499 writer.write(" MISSING="); 500 this.writeToken(writer, this.missing); 501 writer.write(" GAP="); 502 this.writeToken(writer, this.gap); 503 writer.write(" SYMBOLS=\""); 504 if (this.symbols.isEmpty()) { 505 this.symbols.add("0"); 506 this.symbols.add("1"); 507 } 508 for (final Iterator i = this.symbols.iterator(); i.hasNext();) 509 this.writeToken(writer, (String) i.next()); 510 writer.write('"'); 511 if (!this.equate.isEmpty()) { 512 writer.write(" EQUATE=\""); 513 for (final Iterator i = this.equate.entrySet().iterator(); i 514 .hasNext();) { 515 final Map.Entry entry = (Map.Entry) i.next(); 516 this.writeToken(writer, "" + entry.getKey()); 517 writer.write("=("); 518 for (final Iterator j = ((List) entry.getValue()).iterator(); j 519 .hasNext();) 520 this.writeToken(writer, "" + j.next()); 521 writer.write(')'); 522 if (i.hasNext()) 523 writer.write(' '); 524 } 525 writer.write('"'); 526 } 527 if (this.matchChar != null) { 528 writer.write(" MATCHCHAR="); 529 this.writeToken(writer, this.matchChar); 530 } 531 writer.write(this.labels ? " LABELS" : " NOLABELS"); 532 if (this.transposed) 533 writer.write(" TRANSPOSED"); 534 // FIXME Output files, for now, are never interleaved. 535 // if (this.interleaved) 536 // writer.write(" INTERLEAVED"); 537 writer.write(" ITEMS="); 538 if (this.items.isEmpty()) 539 this.items.add("STATES"); 540 if (this.items.size() > 1) 541 writer.write('('); 542 for (final Iterator i = this.items.iterator(); i.hasNext();) { 543 this.writeToken(writer, "" + i.next()); 544 if (i.hasNext()) 545 writer.write(' '); 546 } 547 if (this.items.size() > 1) 548 writer.write(')'); 549 writer.write(" STATESFORMAT="); 550 this.writeToken(writer, this.statesFormat); 551 final boolean reallyUseTokens = (this.tokens || "CONTINUOUS" 552 .equals(this.dataType)) 553 && !("DNA".equals(this.dataType) || "RNA".equals(this.dataType) || "NUCLEOTIDE" 554 .equals(this.dataType)); 555 writer.write(reallyUseTokens ? " TOKENS" : " NOTOKENS"); 556 writer.write(";" + NexusFileFormat.NEW_LINE); 557 558 if (this.eliminateStart > 0 && this.eliminateEnd > 0) { 559 writer.write(" ELIMINATE " + this.eliminateStart + "-" 560 + this.eliminateEnd); 561 writer.write(";" + NexusFileFormat.NEW_LINE); 562 } 563 564 if (this.taxLabels.size() > 0) { 565 writer.write(" TAXLABELS"); 566 for (final Iterator i = this.taxLabels.iterator(); i.hasNext();) { 567 writer.write(' '); 568 this.writeToken(writer, (String) i.next()); 569 } 570 writer.write(";" + NexusFileFormat.NEW_LINE); 571 } 572 573 if (!this.charStateLabels.isEmpty() 574 && !"CONTINUOUS".equals(this.dataType)) { 575 writer.write(" CHARSTATELABELS" + NexusFileFormat.NEW_LINE); 576 for (final Iterator i = this.charStateLabels.entrySet().iterator(); i 577 .hasNext();) { 578 final Map.Entry topEntry = (Map.Entry) i.next(); 579 writer.write('\t'); 580 this.writeToken(writer, "" + topEntry.getKey()); 581 writer.write('\t'); 582 final Object[] parts = (Object[]) topEntry.getValue(); 583 this.writeToken(writer, "" + parts[0]); 584 final List names = (List) parts[1]; 585 if (!names.isEmpty()) { 586 writer.write('/'); 587 for (final Iterator k = names.iterator(); k.hasNext();) { 588 this.writeToken(writer, "" + k.next()); 589 if (k.hasNext()) 590 writer.write(' '); 591 } 592 } 593 if (!i.hasNext()) 594 writer.write(';'); 595 else 596 writer.write(','); 597 writer.write(NexusFileFormat.NEW_LINE); 598 } 599 } 600 601 if (!this.charLabels.isEmpty() && !this.transposed) { 602 writer.write(" CHARLABELS" + NexusFileFormat.NEW_LINE); 603 writer.write('\t'); 604 for (final Iterator i = this.charLabels.iterator(); i.hasNext();) { 605 this.writeToken(writer, "" + i.next()); 606 if (i.hasNext()) 607 writer.write(' '); 608 } 609 writer.write(";" + NexusFileFormat.NEW_LINE); 610 } 611 612 if (!this.stateLabels.isEmpty() && !"CONTINUOUS".equals(this.dataType)) { 613 writer.write(" STATELABELS" + NexusFileFormat.NEW_LINE); 614 for (final Iterator i = this.stateLabels.entrySet().iterator(); i 615 .hasNext();) { 616 final Map.Entry topEntry = (Map.Entry) i.next(); 617 writer.write('\t'); 618 this.writeToken(writer, "" + topEntry.getKey()); 619 writer.write('\t'); 620 final List names = (List) topEntry.getValue(); 621 for (final Iterator k = names.iterator(); k.hasNext();) { 622 this.writeToken(writer, "" + k.next()); 623 if (k.hasNext()) 624 writer.write(' '); 625 } 626 if (!i.hasNext()) 627 writer.write(';'); 628 else 629 writer.write(','); 630 writer.write(NexusFileFormat.NEW_LINE); 631 } 632 } 633 634 // if statesformat=statespresent and items=1, bracket only multi values, 635 // otherwise bracket all values 636 // only space tokens if reallyUseTokens=true 637 writer.write(" MATRIX" + NexusFileFormat.NEW_LINE); 638 for (final Iterator i = this.matrix.entrySet().iterator(); i.hasNext();) { 639 final Map.Entry entry = (Map.Entry) i.next(); 640 writer.write('\t'); 641 this.writeToken(writer, "" + entry.getKey()); 642 writer.write('\t'); 643 for (final Iterator j = ((List) entry.getValue()).iterator(); j 644 .hasNext();) { 645 this.writeMatrixEntry(writer, j.next(), reallyUseTokens); 646 if (reallyUseTokens && j.hasNext()) 647 writer.write(' '); 648 } 649 writer.write(NexusFileFormat.NEW_LINE); 650 } 651 writer.write(";" + NexusFileFormat.NEW_LINE); 652 653 } 654 655 private void writeMatrixEntry(final Writer writer, final Object obj, 656 final boolean reallyUseTokens) throws IOException { 657 if (obj == null) 658 this.writeToken(writer, this.missing); 659 else if (obj instanceof String) 660 this.writeToken(writer, (String) obj); 661 else if (obj instanceof List) { 662 writer.write('('); 663 for (final Iterator k = ((List) obj).iterator(); k.hasNext();) { 664 this.writeMatrixEntry(writer, k.next(), reallyUseTokens); 665 if (k.hasNext() && reallyUseTokens) 666 writer.write(' '); 667 } 668 writer.write(')'); 669 } else if (obj instanceof Set) { 670 writer.write('{'); 671 for (final Iterator k = ((Set) obj).iterator(); k.hasNext();) { 672 this.writeMatrixEntry(writer, k.next(), reallyUseTokens); 673 if (k.hasNext() && reallyUseTokens) 674 writer.write(' '); 675 } 676 writer.write('}'); 677 } 678 } 679}