source: trunk/grails-app/services/nl/tno/metagenomics/FastaService.groovy @ 12

Last change on this file since 12 was 12, checked in by robert@…, 9 years ago

Implemented basic exporting functionality

File size: 23.0 KB
Line 
1package nl.tno.metagenomics
2
3import java.io.BufferedWriter;
4import java.io.File;
5import java.util.ArrayList;
6import java.util.zip.ZipEntry
7import java.util.zip.ZipOutputStream
8import org.codehaus.groovy.grails.commons.ConfigurationHolder
9
10class FastaService {
11        def fileService
12        def fuzzySearchService
13
14        static transactional = true
15
16        /**
17         * Parses uploaded files and checks them for FASTA and QUAL files
18         * @param filenames             List of filenames currently existing in the upload directory
19         * @param onProgress    Closure to execute when progress indicators should be updated.
20         *                                              Has 4 parameters: numFilesProcessed, numBytesProcessed that indicate the number
21         *                                              of files and bytes that have been processed in total. totalFiles, totalBytes indicate the change
22         *                                              in total number of files and bytes (e.g. should take 1 if a new file is added to the list)
23         * @param directory             Directory to move the files to
24         * @return                              Structure with information about the parsed files. The 'success' files are
25         *                                              moved to the given directory
26         *
27         * [
28         *              success: [
29         *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
30         *                      [filename: 'cde.fasta', type: FASTA, numSequences: 140]
31         *                      [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
32         *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
33         *              ],
34         *              failure: [
35         *                      [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized']
36         *              ]
37         * ]
38         *
39         */
40        def parseFiles( ArrayList filenames, Closure onProgress, File directory = null ) {
41                if( filenames.size() == 0 ) {
42                        return [ success: [], failure: [] ];
43                }
44
45                if( !directory ) {
46                        directory = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir )
47                }
48
49                def success = [];
50                def failure = [];
51
52                long filesProcessed = 0;
53                long bytesProcessed = 0;
54
55                // Loop through all filenames
56                for( int i = 0; i < filenames.size(); i++ ) {
57                        def filename = filenames[ i ];
58
59                        if( fileService.isZipFile( filename ) ) {
60                                // ZIP files are extracted and appended to the filenames list.
61                                def newfiles = fileService.extractZipFile( filename, { files, bytes, totalFiles, totalBytes ->
62                                        filesProcessed += files;
63                                        bytesProcessed += bytes;
64
65                                        onProgress( filesProcessed, bytesProcessed, totalFiles, totalBytes );
66                                } );
67                                if( newfiles ) {
68                                        newfiles.each {
69                                                filenames.add( it );
70                                        }
71                                }
72                        } else {
73                                def file = fileService.get( filename );
74                                String filetype = fileService.determineFileType( file );
75
76                                if( !fileTypeValid( filetype ) ) {
77                                        // If files are not valid for parsing, delete them and return a message to the user
78                                        fileService.delete(filename);
79                                        failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: 'File type not accepted' ];
80                                } else {
81                                        try {
82                                                def contents = parseFile( file, filetype, { files, bytes ->
83                                                        filesProcessed += files;
84                                                        bytesProcessed += bytes;
85
86                                                        onProgress( filesProcessed, bytesProcessed, 0, 0 );
87                                                } );
88
89                                                contents.filename = file.getName();
90                                                contents.originalfilename = fileService.originalFilename( contents.filename )
91
92                                                if( contents.success ) {
93                                                        success << contents;
94                                                } else {
95                                                        fileService.delete(filename);
96                                                        failure << contents;
97                                                }
98                                        } catch( Exception e ) {
99                                                // If anything fails during parsing, return an error message
100                                                fileService.delete(filename);
101                                                failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: e.getMessage() ];
102                                        }
103                                }
104                        }
105                }
106
107                return [ success: success, failure: failure ];
108        }
109
110        /**
111         * Matches uploaded fasta and qual files and combines them with the samples they probably belong to
112         * @param parsedFiles   Parsed files
113         * [
114         *              [filename: 'abc.fasta', type: FASTA, numSequences: 190]
115         *              [filename: 'cde.fasta', type: FASTA, numSequences: 140]
116         *              [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
117         *              [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
118         * ]
119         * @param samples               AssaySample objects to which the files should be matched.
120         * @return                              Structure with information about the matching.
121         * [
122         *              [
123         *                      fasta:  [filename: 'abc.fasta', type: FASTA, numSequences: 190],
124         *                      qual:   [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
125         *                      feasibleQuals: [
126         *                              [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
127         *                              [filename: 'def.qual', type: QUAL, numSequences: 190, avgQuality: 21]
128         *                      ]
129         *                      sample: AssaySample object     
130         */
131        def matchFiles( def parsedFiles, def samples ) {
132                def fastas = parsedFiles.findAll { it.type == "fasta" }
133                def quals = parsedFiles.findAll { it.type == "qual" }
134                samples = samples.toList()
135
136                def files = [];
137
138                fastas.each { fastaFile ->
139                        // Remove extension
140                        def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) )
141
142                        // Determine feasible quals (based on number of sequences )
143                        def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences }
144
145                        // Best matching qual file
146                        def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename );
147
148                        def qual = null
149                        if( qualIdx != null )
150                                qual = feasibleQuals[ qualIdx ];
151
152                        // Best matching sample
153                        // TODO: Implement method to search for sample matches in a provided excel sheet
154                        def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name );
155                        def assaySample = null
156                        if( sampleIdx != null ) {
157                                assaySample = samples[ sampleIdx ];
158                        }
159
160                        files << [
161                                                fasta: fastaFile,
162                                                feasibleQuals: feasibleQuals,
163                                                qual: qual,
164                                                assaySample: assaySample
165                                        ]
166                }
167
168                return files;
169
170
171        }
172
173        /**
174         * Determines whether a file can be processed.
175         * @param filetype      Filetype of the file
176         * @see determineFileType()
177         * @return
178         */
179        protected boolean fileTypeValid( String filetype ) {
180                switch( filetype ) {
181                        case "fasta":
182                        case "qual":
183                                return true;
184                        default:
185                                return false;
186                }
187        }
188
189        /**
190         * Parses the given file
191         * @param file                  File to parse
192         * @param filetype              Type of the given file
193         * @param onProgress    Closure to execute when progress indicators should be updated.
194         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
195         *                                              of files and bytes that have been processed in this file (so the first parameter should
196         *                                              only be 1 when the file is finished)
197         *
198         * @return                              List structure. Examples:
199         *
200         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
201         *   [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 36 ]
202         *   [ success: false, filename: 'abc.txt', type: 'txt', message: 'Filetype could not be parsed.' ]
203         */
204        protected def parseFile( File file, String filetype, Closure onProgress ) {
205                switch( filetype ) {
206                        case "fasta":
207                                return parseFasta( file, onProgress );
208                        case "qual":
209                                return parseQual( file, onProgress );
210                        default:
211                                onProgress( 1, file.length() );
212                                return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ]
213                }
214        }
215
216        /**
217         * Parses the given FASTA file
218         * @param file                  File to parse
219         * @param onProgress    Closure to execute when progress indicators should be updated.
220         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
221         *                                              of files and bytes that have been processed in this file (so the first parameter should
222         *                                              only be 1 when the file is finished)
223         * @return                              List structure. Examples:
224         *
225         *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
226         *   [ success: false, filename: 'def.fasta', type: 'fasta', message: 'File is not a valid FASTA file' ]
227         */
228        protected def parseFasta( File file, Closure onProgress ) {
229
230                long startTime = System.nanoTime();
231                log.trace "Start parsing FASTA " + file.getName()
232
233                // Count the number of lines, starting with '>' (and where the following line contains a character other than '>')
234                long numSequences = 0;
235                long bytesProcessed = 0;
236                boolean lookingForSequence = false;
237
238                file.eachLine { line ->
239                        if( line ) {
240                                if( !lookingForSequence && line[0] == '>' ) {
241                                        lookingForSequence = true;
242                                } else if( lookingForSequence ) {
243                                        if( line[0] != '>' ) {
244                                                numSequences++;
245                                                lookingForSequence = false;
246                                        }
247                                }
248
249
250                                // Update progress every time 1MB is processed
251                                bytesProcessed += line.size();
252                                if( bytesProcessed > 1000000 ) {
253                                        onProgress( 0, bytesProcessed );
254                                        bytesProcessed = 0;
255                                }
256                        }
257
258                }
259
260                // Update progress and say we're finished
261                onProgress( 1, bytesProcessed );
262
263                log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
264
265                return [ success: true, type: "fasta", numSequences: numSequences ];
266        }
267
268        /**
269         * Parses the given QUAL file
270         * @param file                  File to parse
271         * @param onProgress    Closure to execute when progress indicators should be updated. 
272         *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
273         *                                              of files and bytes that have been processed in this file (so the first parameter should
274         *                                              only be 1 when the file is finished)
275         * @return                              List structure. Examples:
276         *
277         *   [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 31 ]
278         *   [ success: false, filename: 'def.qual', type: 'qual', message: 'File is not a valid QUAL file' ]
279         */
280        protected def parseQual( File file, Closure onProgress ) {
281                long startTime = System.nanoTime();
282                log.trace "Start parsing QUAL " + file.getName()
283
284                // Count the number of lines, starting with '>'. After we've found such a character, we continue looking for
285                // quality scores
286                long numSequences = 0;
287                long bytesProcessed = 0;
288                def quality = [ quality: 0.0, number: 0L ]
289
290                boolean lookingForFirstQualityScores = false;
291                file.eachLine { line ->
292                        if( line ) {
293                                if( !lookingForFirstQualityScores && line[0] == '>' ) {
294                                        lookingForFirstQualityScores = true;
295                                } else if( lookingForFirstQualityScores ) {
296                                        if( line[0] != '>' ) {
297                                                numSequences++;
298                                                lookingForFirstQualityScores = false;
299
300                                                // Don't compute average quality because it takes too much time
301                                                //quality = updateQuality( quality, line );
302                                        }
303                                } else {
304                                        // Don't compute average quality because it takes too much time
305                                        //quality = updateQuality( quality, line );
306                                }
307
308                                // Update progress every time 1MB is processed
309                                bytesProcessed += line.size();
310                                if( bytesProcessed > 1000000 ) {
311                                        onProgress( 0, bytesProcessed );
312                                        bytesProcessed = 0;
313                                }
314                        }
315
316                }
317
318                // Update progress and say we're finished
319                onProgress( 1, bytesProcessed );
320
321                log.trace "Finished parsing QUAL " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
322
323                return [ success: true, type: "qual", numSequences: numSequences, avgQuality: quality.quality ];
324        }
325
326        /**
327         * Parses the given line and updates the average quality based on the scores
328         * @param quality       [quality: 0.0f, number: 0L]
329         * @param line          String of integer quality scores, separated by a whitespace
330         * @return                      [quality: 0.0f, number: 0L]
331         */
332        protected def updateQuality( def quality, String line ) {
333                // Determine current average
334                List tokens = line.tokenize();
335                Long total = 0;
336
337                tokens.each {
338                        total += Integer.parseInt( it );
339                }
340
341                int numTokens = tokens.size();
342
343                // Update the given average
344                if( numTokens > 0 ) {
345                        quality.number += numTokens;
346                        quality.quality = quality.quality + ( ( total / numTokens as double ) - quality.quality ) / quality.number * numTokens;
347                }
348
349                return quality
350        }
351
352        /**
353         * Moves a fasta and qual file to their permanent location, and returns information about these files
354         *
355         * @param fastaFile                     Filename of the fasta file in the temporary directory
356         * @param qualFile                      Filename of the fasta file in the temporary directory
357         * @param processedFiles        Structure with data about uploaded files and matches
358         * @return      [ fasta: <fasta filename>, qual: <qual filename>, numSequences: <number of sequences>, avgQuality: <average quality> ]
359         */
360        public def savePermanent( String fastaFile, String qualFile, def processedFiles ) {
361                File permanentDirectory = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir );
362                def returnStructure = [:];
363
364                if( fileService.fileExists( fastaFile ) ) {
365                        // Lookup the original filename
366                        def fastaData = processedFiles.parsed.success.find { it.filename == fastaFile };
367                        if( fastaData ) {
368                                returnStructure.fasta = fileService.moveFileToUploadDir( fileService.get( fastaFile ), fastaData.originalfilename, permanentDirectory );
369                                returnStructure.numSequences = fastaData.numSequences;
370                        } else {
371                                throw new Exception( "Fasta file wasn't uploaded the right way. Maybe the session has expired/" );
372                        }
373                } else {
374                        // If the file doesn't exist, we can't save anything to the database.
375                        throw new Exception( "Fasta file to save doesn't exist on disk" );
376                }
377
378                if( qualFile && fileService.fileExists( qualFile ) ) {
379                        // Lookup the original filename
380                        def qualData = processedFiles.parsed.success.find { it.filename == qualFile };
381
382                        if( qualData ) {
383                                returnStructure.qual = fileService.moveFileToUploadDir( fileService.get(qualFile ), qualData.originalfilename, permanentDirectory );
384                                returnStructure.avgQuality = qualData.avgQuality
385                        } else {
386                                // Delete the uploaded fasta file, since this is a serious error
387                                fileService.delete( returnStructure.fasta, permanentDirectory );
388                                throw new Exception( "Qual file wasn't uploaded the right way. Maybe the session has expired" );
389                        }
390                } else {
391                        // If the file doesn't exist, we don't save any quality information
392                        returnStructure.qual = null
393                        returnStructure.avgQuality = 0;
394                }
395
396                return returnStructure;
397        }
398
399        /**
400         * Exports the fasta data of a list of assaysamples
401         * @param assaySamples  Assaysamples to export
402         * @param outStream             Outputstream to send the data to
403         * @return
404         */
405        public def export( List assaySamples, OutputStream outStream, String name ) {
406                if( !assaySamples || assaySamples.size() == 0 )
407                        return false;
408
409                // Determine the directory the uploaded files are stored in
410                File permanentDirectory = fileService.absolutePath( ConfigurationHolder.config.metagenomics.fileDir );
411
412                // First check whether qual files should be exported or not
413                // It is only exported if qual scores are available for all sequences
414                def exportQual = ( assaySamples*.numSequences().sum() == assaySamples*.numQualScores().sum() );
415
416                // First create tags for every sample
417                def tags = [];
418
419                // Determine new tag length. Since we can use 4 characters per
420                // tag position, we only have to use 4log( #samples)
421                int tagLength = Math.ceil( Math.log( assaySamples.size() ) / Math.log( 4 ) )
422                int tagNumber = 0;
423
424                assaySamples.each { assaySample ->
425                        if( assaySample.numSequences() > 0 ) {
426                                // Create a new tag for this assaysample
427                                def tag = createTag( tagLength, tagNumber++);
428
429                                // Save the tag for exporting
430                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, tag: tag]
431                        }
432                }
433
434                // Now create zip file for fasta and qual files
435                ZipOutputStream zipFile = new ZipOutputStream( new BufferedOutputStream( outStream ) );
436                BufferedWriter zipWriter = new BufferedWriter( new OutputStreamWriter( zipFile ) );
437               
438                // We have to loop twice through the sequenceData, since we can't write part of the sequence
439                // file and part of the qual files mixed. We have to write the full sequence file first.
440                try {
441                        zipFile.putNextEntry( new ZipEntry( name + ".fasta" ) );
442                       
443                        assaySamples.each { assaySample ->
444                                if( assaySample.numSequences() > 0 ) {
445                                        def currentTag = tags.find { it.assaySampleId == assaySample.id };
446
447                                        assaySample.sequenceData.each { sequenceData ->
448                                                copyFastaFileForExport( fileService.get( sequenceData.sequenceFile, permanentDirectory ), currentTag.tag, zipWriter)
449                                        }
450                                }
451                        }
452                        zipWriter.flush();
453                        zipFile.closeEntry();
454
455                        if( exportQual ) {
456                                zipFile.putNextEntry( new ZipEntry( name + ".qual" ) );
457
458                                assaySamples.each { assaySample ->
459                                        if( assaySample.numSequences() > 0 ) {
460                                                def currentTag = tags.find { it.assaySampleId == assaySample.id };
461
462                                                assaySample.sequenceData.each { sequenceData ->
463                                                        copyQualFileForExport( fileService.get( sequenceData.qualityFile, permanentDirectory ), currentTag.tag, zipWriter)
464                                                }
465                                        }
466                                }
467                               
468                                zipWriter.flush();
469                                zipFile.closeEntry();
470                        }
471
472                } catch( Exception e ) {
473                        log.error "Error while writing to fastafile or qualfile: " + e.getMessage();
474                } finally {
475                        // Always close zip entry
476                        try {
477                                zipFile.closeEntry();
478                        } catch( Exception e ) {
479                                log.error "Error while closing zip entry for fasta and qual: " + e.getMessage();
480                        }
481                }
482               
483                zipFile.close();
484
485                // Export a tab delimited file with tags
486                //exportSampleTagFile( tags );
487        }
488
489        /**
490         * Creates a unique tag for the given number
491         * @param length
492         * @param tagNumber
493         * @return
494         */
495        public String createTag( int length, int tagNumber ) {
496                def chars = ["C", "A", "G", "T"];
497                def numChars = chars.size();
498
499                if( tagNumber > numChars ** length )
500                        throw new Exception( "Given tag number (" + tagNumber + ") is too large for the specified length (" + length + ")")
501
502                String tag = "";
503
504                for( def i = 0; i < length; i++ ) {
505                        int currentChar = tagNumber % numChars
506
507                        tag = chars[ currentChar ] + tag;
508
509                        tagNumber = Math.floor( tagNumber / numChars );
510                }
511
512                return tag
513        }
514
515        /**
516         * Copies the contents of the given sequence file to the output file and prepends the tag to every sequences
517         * @param inFile        Filename of the file to be read
518         * @param tag           
519         * @param outWriter
520         * @return
521         */
522        protected boolean copyFastaFileForExport( File inFile, String tag, BufferedWriter outWriter ) {
523                // Walk through the lines in the file, starting with '>'
524                // (and where the following line contains a character other than '>')
525
526                try {
527                        BufferedReader inReader = new BufferedReader( new FileReader( inFile ) );
528
529                        String line = null
530                        String newLine = null
531                        String sequence = "";
532
533                        def lengthPattern = ~/length=(\d+)/
534                        def lengthMatches
535                        int length = 0;
536                        int tagLength = tag.size();
537
538                        while( ( line = inReader.readLine()) != null) {
539                                if( line.size() == 0 ) {
540                                        // Print the sequence we collected, before writing the empty line
541                                        printSequence( outWriter, sequence, tag );
542                                        sequence = "";
543
544                                        // Empty line
545                                        outWriter.newLine();
546                                } else if( line[ 0 ] == '>' ) {
547                                        // Print the sequence we collected, before writing the new comments tag
548                                        printSequence( outWriter, sequence, tag );
549                                        sequence = "";
550
551                                        // Comments line: replace length=### with the
552                                        // updated length, and put the line in the
553                                        lengthMatches = ( line =~ lengthPattern );
554                                        if( lengthMatches ) {
555                                                length = Integer.valueOf( lengthMatches[0][1] ) + tagLength;
556                                                newLine = lengthMatches.replaceAll( "length=" + length );
557                                        }
558
559                                        outWriter.write(newLine);
560                                        outWriter.newLine();
561                                } else {
562                                        // This is part of the sequence. We collect the whole sequence and
563                                        // determine in the end how to write it to the file
564                                        sequence += line;
565                                }
566                        }
567
568                        // Print the sequence we collected, before ending the file
569                        printSequence( outWriter, sequence, tag );
570                        sequence = "";
571
572                } catch( Exception e ) {
573                        log.error( "An error occurred while copying contents from " + inFile.getName() + ": " + e.getMessage() );
574                        return false;
575                }
576        }
577
578        /**
579         * Prints a sequence to the output file
580         * @param outWriter
581         * @param sequence
582         * @param tag
583         */
584        private void printSequence( BufferedWriter outWriter, String sequence, String tag, int maxWidth = 60 ) {
585                // If no sequence is given, also don't prepend it with the tag
586                if( sequence.size() == 0 )
587                        return
588
589                // Prepend the tag to the sequence
590                sequence = tag + sequence;
591
592                // Write the sequence with a width of maxWidth characters per line
593                while( sequence ) {
594                        if( sequence.size() > maxWidth ) {
595                                outWriter.write( sequence[0..maxWidth-1] );
596                                sequence = sequence[maxWidth..-1]
597                        } else {
598                                outWriter.write( sequence );
599                                sequence = null;
600                        }
601                        outWriter.newLine();
602                }
603        }
604
605        /**
606         * Copies the contents of the given qual file to the output file and prepends the tag quality score to every sequence.
607         * For every tag character '40' is prepended to the qual scores
608         *
609         * @param inFile        Filename of the file to be read
610         * @param tag
611         * @param outWriter
612         * @return
613         */
614        protected boolean copyQualFileForExport( File inFile, String tag, BufferedWriter outWriter ) {
615                // Walk through the lines in the file, starting with '>'
616                // (and where the following line contains a character other than '>')
617                try {
618                        BufferedReader inReader = new BufferedReader( new FileReader( inFile ) );
619
620                        String line = null
621                        String newLine = null
622                        List<Integer> qualScores = []
623
624                        def lengthPattern = ~/length=(\d+)/
625                        def lengthMatches
626                        int length = 0;
627                        int tagLength = tag.size();
628
629                        while( ( line = inReader.readLine()) != null) {
630                                if( line.size() == 0 ) {
631                                        // Print the quality scores we collected, before writing the empty line
632                                        printQualScore( outWriter, qualScores, tagLength );
633                                        qualScores = [];
634
635                                        // Empty line
636                                        outWriter.newLine();
637                                } else if( line[ 0 ] == '>' ) {
638                                        // Print the quality scores we collected, before writing the empty line
639                                        printQualScore( outWriter, qualScores, tagLength );
640                                        qualScores = [];
641
642                                        // Comments line: replace length=### with the
643                                        // updated length, and put the line in the
644                                        lengthMatches = ( line =~ lengthPattern );
645                                        if( lengthMatches ) {
646                                                length = Integer.valueOf( lengthMatches[0][1] ) + tagLength;
647                                                newLine = lengthMatches.replaceAll( "length=" + length );
648                                        }
649
650                                        outWriter.write(newLine);
651                                        outWriter.newLine();
652                                } else {
653                                        // This is part of the quality score. We collect the whole set of quality
654                                        // scores and determine in the end how to write it to the file
655                                        qualScores += line.split( " " ).collect {
656                                                if( !it.isInteger() )
657                                                        return 0;
658                                                else
659                                                        return Integer.parseInt( it );
660                                        };
661                                }
662                        }
663
664                        // Print the quality scores we collected, before ending the file
665                        printQualScore( outWriter, qualScores, tagLength );
666                        qualScores = [];
667
668                } catch( Exception e ) {
669                        log.error( "An error occurred while copying contents from " + inFile.getName() + ": " + e.getMessage() );
670                        return false;
671                }
672        }
673
674        /**
675         * Prints a sequence to the output file
676         * @param outWriter
677         * @param sequence
678         * @param tag
679         */
680        private void printQualScore( BufferedWriter outWriter, List<Integer> qualScores, int tagLength, int maxWidth = 60 ) {
681                // If no qualScores are given, also don't prepend it with the tag
682                if( qualScores.size() == 0 )
683                        return
684
685                // Prepend the tag to the sequence
686                qualScores = Collections.nCopies( tagLength, 40 ) + qualScores;
687
688                // Write the sequence with a width of maxWidth characters per line
689                while( qualScores ) {
690                        if( qualScores.size() > maxWidth ) {
691                                outWriter.write( qualScores[0..maxWidth-1].join( " " ) );
692                                qualScores = qualScores[maxWidth..-1]
693                        } else {
694                                outWriter.write( qualScores.join( " " ) );
695                                qualScores = null;
696                        }
697                        outWriter.newLine();
698                }
699        }
700
701}
Note: See TracBrowser for help on using the repository browser.