001/* 002 * Copyright 2018 Leibniz-Institut für Analytische Wissenschaften – ISAS – e.V.. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.isas.mztab2.io; 017 018import de.isas.mztab2.model.ColumnParameterMapping; 019import de.isas.mztab2.model.Comment; 020import de.isas.mztab2.model.Metadata; 021import de.isas.mztab2.model.MsRun; 022import de.isas.mztab2.model.MzTab; 023import de.isas.mztab2.model.SmallMoleculeEvidence; 024import de.isas.mztab2.model.SmallMoleculeFeature; 025import de.isas.mztab2.model.SmallMoleculeSummary; 026import java.io.*; 027import java.net.URI; 028import java.net.URL; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.HashSet; 032import java.util.Optional; 033import java.util.Set; 034import java.util.SortedMap; 035import java.util.TreeMap; 036import java.util.regex.Matcher; 037import java.util.regex.Pattern; 038import java.util.stream.Collectors; 039import java.util.zip.GZIPInputStream; 040import uk.ac.ebi.pride.jmztab2.model.IMZTabColumn; 041import uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory; 042import static uk.ac.ebi.pride.jmztab2.model.MZTabConstants.NEW_LINE; 043import static uk.ac.ebi.pride.jmztab2.model.MZTabConstants.REGEX_DEFAULT_RELIABILITY; 044import static uk.ac.ebi.pride.jmztab2.model.MZTabConstants.TAB; 045import uk.ac.ebi.pride.jmztab2.model.MZTabStringUtils; 046import uk.ac.ebi.pride.jmztab2.model.Section; 047import static uk.ac.ebi.pride.jmztab2.utils.MZTabProperties.*; 048import uk.ac.ebi.pride.jmztab2.utils.errors.FormatErrorType; 049import uk.ac.ebi.pride.jmztab2.utils.errors.LogicalErrorType; 050import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabError; 051import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorList; 052import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorOverflowException; 053import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorType; 054import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabException; 055import uk.ac.ebi.pride.jmztab2.utils.parser.COMLineParser; 056import uk.ac.ebi.pride.jmztab2.utils.parser.MTDLineParser; 057import uk.ac.ebi.pride.jmztab2.utils.parser.MZTabParserContext; 058import uk.ac.ebi.pride.jmztab2.utils.parser.PositionMapping; 059import uk.ac.ebi.pride.jmztab2.utils.parser.SEHLineParser; 060import uk.ac.ebi.pride.jmztab2.utils.parser.SFHLineParser; 061import uk.ac.ebi.pride.jmztab2.utils.parser.SMELineParser; 062import uk.ac.ebi.pride.jmztab2.utils.parser.SMFLineParser; 063import uk.ac.ebi.pride.jmztab2.utils.parser.SMHLineParser; 064import uk.ac.ebi.pride.jmztab2.utils.parser.SMLLineParser; 065 066/** 067 * 068 * MZTabFileParser provides reading functionality of the mzTab file. During the 069 * parsing process, minimal integrity checks are preformed. 070 * 071 * @author qingwei 072 * @author nilshoffmann 073 * 074 * @since 21/02/13 075 * 076 */ 077public class MzTabFileParser { 078 079 private MzTab mzTabFile; 080 private URI tabFile; 081 082 private MZTabErrorList errorList; 083 private MZTabParserContext context; 084 085 /** 086 * Create a new {@code MZTabFileParser} for the given file. 087 * 088 * @param tabFile the MZTab file. The file SHOULD not be null and MUST exist 089 * @throws java.lang.IllegalArgumentException if the provided argument in 090 * invalid. 091 */ 092 public MzTabFileParser(File tabFile) throws IllegalArgumentException { 093 this(tabFile.toURI()); 094 } 095 096 /** 097 * Create a new {@code MZTabFileParser} for the given file URI. 098 * 099 * @param tabFileUri the MZTab file URI. The file SHOULD not be null and 100 * MUST exist {@link uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorList} 101 * return by 102 * {@link de.isas.mztab2.io.MzTabFileParser#getErrorList()} 103 * @throws java.lang.IllegalArgumentException if the provided argument in 104 * invalid. 105 */ 106 public MzTabFileParser(URI tabFileUri) throws IllegalArgumentException { 107 if (tabFileUri == null) { 108 throw new IllegalArgumentException( 109 "MZTab file uri must not be null!"); 110 } 111 if (("file".equals(tabFileUri.getScheme()) && !new File(tabFileUri). 112 exists())) { 113 throw new IllegalArgumentException("MZTab File URI " + tabFileUri. 114 toASCIIString() + " does not exist!"); 115 } 116 117 this.tabFile = tabFileUri; 118 } 119 120 /** 121 * Create a new {@code MZTabParserContext} and {@code MZTabErrorList} for 122 * the given file URI. Parsing output and errors are written to the provided 123 * {@link java.io.OutputStream}. 124 * 125 * @param out the output stream for parsing messages 126 * @param level the minimum error level to report errors for 127 * @param maxErrorCount the maximum number of errors to report in the 128 * {@link uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorList} return by 129 * {@link de.isas.mztab2.io.MzTabFileParser#getErrorList()} 130 * @return the error list 131 * @throws java.io.IOException if any io related errors occur. 132 */ 133 public MZTabErrorList parse(OutputStream out, MZTabErrorType.Level level, 134 int maxErrorCount) throws IOException { 135 try { 136 context = new MZTabParserContext(); 137 errorList = new MZTabErrorList(level, maxErrorCount); 138 check(); 139 refine(); 140 } catch (MZTabException e) { 141 out.write(e.getMessage().getBytes()); 142 try (PrintStream ps = new PrintStream(out)) { 143 e.printStackTrace(ps); 144 } 145 errorList.add(e.getError()); 146 } catch (MZTabErrorOverflowException e) { 147 try (PrintStream ps = new PrintStream(out)) { 148 e.printStackTrace(ps); 149 } 150 out.write(e.getMessage().getBytes()); 151 } 152 153 errorList.print(out); 154 if (mzTabFile != null && errorList.isEmpty()) { 155 out.write( 156 ("No structural or logical errors in " + tabFile + " file!" + NEW_LINE). 157 getBytes()); 158 } 159 return errorList; 160 } 161 162 /** 163 * Create a new {@code MZTabParserContext} and {@code MZTabErrorList} for 164 * the given file URI. Parsing output and errors are written to the provided 165 * {@link java.io.OutputStream}. Reports up to 166 * {@link uk.ac.ebi.pride.jmztab2.utils.MZTabProperties#MAX_ERROR_COUNT} 167 * errors. 168 * 169 * @param out the output stream for parsing messages 170 * @param level the minimum error level to report errors for 171 * @return the error list 172 * @throws java.io.IOException if any io related errors occur. 173 */ 174 public MZTabErrorList parse(OutputStream out, MZTabErrorType.Level level) throws IOException { 175 return parse(out, level, MAX_ERROR_COUNT); 176 } 177 178 /** 179 * Create a new {@code MZTabParserContext} and {@code MZTabErrorList} for 180 * the given file URI. Parsing output and errors are written to the provided 181 * {@link java.io.OutputStream}. Reports up to 182 * {@link uk.ac.ebi.pride.jmztab2.utils.MZTabProperties#MAX_ERROR_COUNT} 183 * errors on level 184 * {@link uk.ac.ebi.pride.jmztab2.utils.MZTabProperties#LEVEL}. 185 * 186 * @param out the output stream for parsing messages 187 * @return the error list 188 * @throws java.io.IOException if any io related errors occur. 189 */ 190 public MZTabErrorList parse(OutputStream out) throws IOException { 191 return parse(out, LEVEL, MAX_ERROR_COUNT); 192 } 193 194 /** 195 * <p> 196 * Getter for the field <code>errorList</code>.</p> 197 * 198 * @return a {@link uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorList} 199 * object. 200 */ 201 public MZTabErrorList getErrorList() { 202 return errorList; 203 } 204 205 private Section getSection(String line) { 206 String[] items = line.split("\\s*" + TAB + "\\s*"); 207 String section = items[0].trim(); 208 return Section.findSection(section); 209 } 210 211 private BufferedReader readFile(URI tabFile) throws IOException { 212 BufferedReader reader; 213 214 InputStream is; 215 File file = new File(tabFile); 216 if (file.isFile()) { 217 is = new FileInputStream(file); 218 } else { 219 URL tabFileUrl = tabFile.toURL(); 220 is = tabFileUrl.openStream(); 221 } 222 if (tabFile.getPath(). 223 endsWith(".gz")) { 224 reader = new BufferedReader(new InputStreamReader( 225 new GZIPInputStream(is), ENCODE)); 226 } else { 227 reader = new BufferedReader(new InputStreamReader( 228 is, ENCODE)); 229 } 230 231 return reader; 232 } 233 234 private String subString(String source) { 235 int length = 20; 236 237 if (length >= source.length()) { 238 return source; 239 } else { 240 return source.substring(0, length - 1) + "..."; 241 } 242 } 243 244 /** 245 * refine all MZTabFile consistency correct. 246 */ 247 private void refine() throws MZTabException, MZTabErrorOverflowException { 248 if (mzTabFile == null) { 249 return; 250 } 251 252 Metadata metadata = mzTabFile.getMetadata(); 253 254 //If ms_run[1-n]-hash is present, ms_run[1-n]-hash_method SHOULD also be present 255 for (MsRun msRun : metadata.getMsRun()) { 256 if (msRun.getHash() != null && msRun.getHashMethod() == null) { 257 throw new MZTabException(new MZTabError( 258 LogicalErrorType.MsRunHashMethodNotDefined, -1, msRun. 259 getId(). 260 toString())); 261 } 262 } 263 } 264 265 /** 266 * Query {@link MZTabErrorList} to check exist errors or not. 267 * 268 * @throws java.io.IOException 269 * @throws uk.ac.ebi.pride.jmztab.utils.errors.MZTabException during parsing 270 * of metadata, 271 * protein/peptide/small_molecule/small_molecule_feature/small_molecule_evidence 272 * header lines, if there exist any errors. 273 * @throws uk.ac.ebi.pride.jmztab.utils.errors.MZTabErrorOverflowException 274 * when too many errors are detected, as defined by the mztab.properties 275 * file mztab.max_error_count parameter. 276 */ 277 private void check() throws IOException, MZTabException, MZTabErrorOverflowException { 278 COMLineParser comParser = new COMLineParser(context); 279 MTDLineParser mtdParser = new MTDLineParser(context); 280 SMHLineParser smhParser = null; 281 SMLLineParser smlParser = null; 282 SFHLineParser sfhParser = null; 283 SMFLineParser smfParser = null; 284 SEHLineParser sehParser = null; 285 SMELineParser smeParser = null; 286 287 SortedMap<Integer, Comment> commentMap = new TreeMap<>(); 288 SortedMap<Integer, SmallMoleculeSummary> smallMoleculeSummaryMap = new TreeMap<>(); 289 SortedMap<Integer, SmallMoleculeFeature> smallMoleculeFeatureMap = new TreeMap<>(); 290 SortedMap<Integer, SmallMoleculeEvidence> smallMoleculeEvidenceMap = new TreeMap<>(); 291 292 PositionMapping smlPositionMapping = null; 293 PositionMapping smfPositionMapping = null; 294 PositionMapping smePositionMapping = null; 295 296 String line; 297 int highWaterMark = 1; 298 int lineNumber = 0; 299 Section section; 300 try (BufferedReader reader = readFile(tabFile)) { 301 while ((line = reader.readLine()) != null) { 302 try { 303 lineNumber++; 304 305 if (MZTabStringUtils.isEmpty(line)) { 306 continue; 307 } 308 309 if (line.startsWith(Section.Comment.getPrefix())) { 310 comParser.parse(lineNumber, line, errorList); 311 commentMap.put(lineNumber, comParser.getComment()); 312 continue; 313 } 314 315 section = getSection(line); 316 if (section == null) { 317 MZTabError sectionNullError = new MZTabError( 318 FormatErrorType.LinePrefix, lineNumber, 319 subString(line)); 320 throw new MZTabException(sectionNullError); 321 } 322 if (section.getLevel() < highWaterMark) { 323 Section currentSection = Section.findSection( 324 highWaterMark); 325 MZTabError sectionLineOrderError = new MZTabError( 326 LogicalErrorType.LineOrder, lineNumber, 327 currentSection.getName(), section.getName()); 328 throw new MZTabException(sectionLineOrderError); 329 } 330 331 highWaterMark = section.getLevel(); 332 333 switch (highWaterMark) { 334 case 1: 335 // metadata section. 336 mtdParser.parse(lineNumber, line, errorList); 337 break; 338 case 8: 339 if (smhParser != null) { 340 MZTabError error = new MZTabError( 341 LogicalErrorType.HeaderLine, 342 lineNumber, subString(line)); 343 // header line only display once! 344 throw new MZTabException(error); 345 } 346 347 // small molecule header section 348 smhParser = new SMHLineParser(context, mtdParser. 349 getMetadata()); 350 smhParser.parse(lineNumber, line, errorList); 351 smlPositionMapping = new PositionMapping(smhParser. 352 getFactory(), line); 353 354 // tell system to continue check small molecule data line. 355 highWaterMark = 9; 356 break; 357 case 9: 358 if (smhParser == null) { 359 // header line should be check first. 360 throw new MZTabException(new MZTabError( 361 LogicalErrorType.NoHeaderLine, 362 lineNumber, subString(line))); 363 } 364 365 if (smlParser == null) { 366 smlParser = new SMLLineParser(context, 367 smhParser. 368 getFactory(), 369 smlPositionMapping, mtdParser.getMetadata(), 370 errorList); 371 } 372 smlParser.parse(lineNumber, line, errorList); 373 smallMoleculeSummaryMap.put(lineNumber, smlParser. 374 getRecord()); 375 376 break; 377 case 10: 378 if (sfhParser != null) { 379 // header line only display once! 380 throw new MZTabException(new MZTabError( 381 LogicalErrorType.HeaderLine, 382 lineNumber, subString(line))); 383 } 384 385 // small molecule header section 386 sfhParser = new SFHLineParser(context, mtdParser. 387 getMetadata()); 388 sfhParser.parse(lineNumber, line, errorList); 389 smfPositionMapping = new PositionMapping(sfhParser. 390 getFactory(), line); 391 392 // tell system to continue check small molecule data line. 393 highWaterMark = 11; 394 break; 395 case 11: 396 if (sfhParser == null) { 397 // header line should be check first. 398 throw new MZTabException(new MZTabError( 399 LogicalErrorType.NoHeaderLine, 400 lineNumber, subString(line))); 401 } 402 403 if (smfParser == null) { 404 smfParser = new SMFLineParser(context, 405 sfhParser. 406 getFactory(), 407 smfPositionMapping, mtdParser.getMetadata(), 408 errorList); 409 } 410 smfParser.parse(lineNumber, line, errorList); 411 smallMoleculeFeatureMap.put(lineNumber, smfParser. 412 getRecord()); 413 414 break; 415 case 12: 416 if (sehParser != null) { 417 // header line only display once! 418 throw new MZTabException(new MZTabError( 419 LogicalErrorType.HeaderLine, 420 lineNumber, subString(line))); 421 } 422 423 // small molecule header section 424 sehParser = new SEHLineParser(context, mtdParser. 425 getMetadata()); 426 sehParser.parse(lineNumber, line, errorList); 427 smePositionMapping = new PositionMapping(sehParser. 428 getFactory(), line); 429 430 // tell system to continue check small molecule data line. 431 highWaterMark = 13; 432 break; 433 case 13: 434 if (sehParser == null) { 435 // header line should be check first. 436 throw new MZTabException(new MZTabError( 437 LogicalErrorType.NoHeaderLine, 438 lineNumber, subString(line))); 439 } 440 441 if (smeParser == null) { 442 smeParser = new SMELineParser(context, 443 sehParser. 444 getFactory(), 445 smePositionMapping, mtdParser.getMetadata(), 446 errorList); 447 } 448 smeParser.parse(lineNumber, line, errorList); 449 smallMoleculeEvidenceMap.put(lineNumber, smeParser. 450 getRecord()); 451 452 break; 453 default: 454 throw new IllegalArgumentException( 455 "Unknown section level " + highWaterMark); 456 } 457 } catch (NullPointerException npe) { 458 throw new MZTabException(new MZTabError( 459 LogicalErrorType.NULL, 460 lineNumber, subString(line)), npe); 461 } 462 } 463 464 } 465 466 mtdParser.refineNormalMetadata(); 467 468 if (errorList.isEmpty()) { 469 mzTabFile = new MzTab(); 470 mzTabFile.metadata(mtdParser.getMetadata()); 471 for (Integer id : commentMap.keySet()) { 472 mzTabFile.addCommentItem(commentMap.get(id)); 473 } 474 475 if (smallMoleculeSummaryMap.isEmpty()) { 476 errorList.add(new MZTabError( 477 LogicalErrorType.NoSmallMoleculeSummarySection, -1)); 478 } 479 if (smlParser != null) { 480 481 for (Integer id : smallMoleculeSummaryMap.keySet()) { 482 mzTabFile.addSmallMoleculeSummaryItem( 483 smallMoleculeSummaryMap.get( 484 id)); 485 } 486 //check that reliability values are correct 487 if (mzTabFile.getMetadata(). 488 getSmallMoleculeIdentificationReliability() == null) { 489 Pattern p = Pattern.compile(REGEX_DEFAULT_RELIABILITY); 490 for (SmallMoleculeSummary smi : mzTabFile. 491 getSmallMoleculeSummary()) { 492 String reliability = smi.getReliability(); 493 Matcher matcher = p.matcher(reliability); 494 if (!matcher.matches()) { 495 errorList.add(new MZTabError( 496 FormatErrorType.RegexMismatch, -1, 497 SmallMoleculeSummary.Properties.reliability. 498 getPropertyName(), reliability, 499 MzTab.Properties.smallMoleculeSummary. 500 getPropertyName(), "" + smi.getSmlId(), 501 REGEX_DEFAULT_RELIABILITY)); 502 } 503 } 504 } 505 checkColunitMapping(smhParser.getFactory(), Optional.ofNullable( 506 mzTabFile. 507 getMetadata(). 508 getColunitSmallMolecule()), 509 Metadata.Properties.colunitSmallMolecule, 510 MzTab.Properties.smallMoleculeSummary); 511 } 512 513 if (smallMoleculeFeatureMap.isEmpty() && !smallMoleculeSummaryMap. 514 isEmpty()) { 515 errorList.add(new MZTabError( 516 LogicalErrorType.NoSmallMoleculeFeatureSection, -1)); 517 } 518 if (smfParser != null) { 519 for (Integer id : smallMoleculeFeatureMap.keySet()) { 520 SmallMoleculeFeature smf 521 = smallMoleculeFeatureMap.get( 522 id); 523 mzTabFile.addSmallMoleculeFeatureItem(smf); 524 } 525 if (smallMoleculeFeatureMap.size() > 0 && mzTabFile. 526 getMetadata(). 527 getSmallMoleculeFeatureQuantificationUnit() == null) { 528 errorList.add(new MZTabError( 529 LogicalErrorType.NoSmallMoleculeFeatureQuantificationUnit, 530 -1)); 531 } 532 checkColunitMapping(sfhParser.getFactory(), Optional.ofNullable( 533 mzTabFile. 534 getMetadata(). 535 getColunitSmallMoleculeFeature()), 536 Metadata.Properties.colunitSmallMoleculeFeature, 537 MzTab.Properties.smallMoleculeFeature); 538 } 539 if (smallMoleculeEvidenceMap.isEmpty() && !smallMoleculeSummaryMap. 540 isEmpty()) { 541 errorList.add(new MZTabError( 542 LogicalErrorType.NoSmallMoleculeEvidenceSection, -1)); 543 } 544 if (smeParser != null) { 545 for (Integer id : smallMoleculeEvidenceMap.keySet()) { 546 mzTabFile.addSmallMoleculeEvidenceItem( 547 smallMoleculeEvidenceMap.get( 548 id)); 549 } 550 checkColunitMapping(sehParser.getFactory(), Optional.ofNullable( 551 mzTabFile. 552 getMetadata(). 553 getColunitSmallMoleculeEvidence()), 554 Metadata.Properties.colunitSmallMoleculeEvidence, 555 MzTab.Properties.smallMoleculeEvidence 556 ); 557 } 558 //check ID refs, starting at SML level 559 if (smlParser != null && smfParser != null) { 560 for (Integer id : smallMoleculeSummaryMap.keySet()) { 561 SmallMoleculeSummary sms = smallMoleculeSummaryMap.get(id); 562 Set<Integer> smfIdRefs = new HashSet<>(sms.getSmfIdRefs()); 563 Set<Integer> definedIds = smallMoleculeFeatureMap.values(). 564 stream(). 565 map((t) -> 566 { 567 return t.getSmfId(); 568 }). 569 collect(Collectors.toSet()); 570 smfIdRefs.removeAll(definedIds); 571 if (!smfIdRefs.isEmpty()) { 572 for (Integer smfRefId : smfIdRefs) { 573 //raise a warning about unmatched SMF id 574 //Reference id "{0}" for column "{1}" from element "{2}" in section "{3}" to section "{4}" must have a matching element defined. 575 errorList.add(new MZTabError( 576 LogicalErrorType.UnknownRefId, -1, "" + smfRefId, 577 SmallMoleculeSummary.Properties.smfIdRefs. 578 getPropertyName(), "" + sms.getSmlId(), 579 MzTab.Properties.smallMoleculeSummary. 580 getPropertyName(), 581 MzTab.Properties.smallMoleculeFeature. 582 getPropertyName())); 583 } 584 } 585 } 586 if (smeParser != null) { 587 for (Integer id : smallMoleculeFeatureMap.keySet()) { 588 SmallMoleculeFeature smf = smallMoleculeFeatureMap.get( 589 id); 590 Set<Integer> smeIdRefs = new HashSet<>(smf. 591 getSmeIdRefs()); 592 Set<Integer> definedIds = smallMoleculeEvidenceMap. 593 values(). 594 stream(). 595 map((t) -> 596 { 597 return t.getSmeId(); 598 }). 599 collect(Collectors.toSet()); 600 smeIdRefs.removeAll(definedIds); 601 if (!smeIdRefs.isEmpty()) { 602 for (Integer smeRefId : smeIdRefs) { 603 //raise a warning about unmatched SMF id 604 //Reference id "{0}" for column "{1}" from element "{2}" in section "{3}" to section "{4}" must have a matching element defined. 605 errorList.add(new MZTabError( 606 LogicalErrorType.UnknownRefId, -1, 607 "" + smeRefId, 608 SmallMoleculeFeature.Properties.smeIdRefs. 609 getPropertyName(), "" + smf.getSmfId(), 610 MzTab.Properties.smallMoleculeFeature. 611 getPropertyName(), 612 MzTab.Properties.smallMoleculeEvidence. 613 getPropertyName())); 614 } 615 } 616 } 617 } 618 } 619 } 620 621 } 622 623 protected void checkColunitMapping(MZTabColumnFactory columnFactory, 624 Optional<Collection<ColumnParameterMapping>> columnParameterMapping, 625 Metadata.Properties colUnitProperty, MzTab.Properties mzTabSection) { 626 columnParameterMapping.orElse(Collections.emptyList()). 627 forEach((colUnit) -> 628 { 629 String columnName = colUnit.getColumnName(); 630 IMZTabColumn column = columnFactory.findColumnByHeader( 631 columnName); 632 if (column == null) { 633 errorList.add(new MZTabError( 634 FormatErrorType.ColUnit, -1, 635 colUnitProperty. 636 getPropertyName(), columnName, 637 mzTabSection. 638 getPropertyName())); 639 } 640 }); 641 } 642 643 /** 644 * <p> 645 * getMZTabFile.</p> 646 * 647 * @return a {@link de.isas.mztab2.model.MzTab} object. 648 */ 649 public MzTab getMZTabFile() { 650 return mzTabFile; 651 } 652}