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}