diff CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/opt/bbmap-39.01-1/current/gff/CutGff.java @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/opt/bbmap-39.01-1/current/gff/CutGff.java	Tue Mar 18 16:23:26 2025 -0400
@@ -0,0 +1,808 @@
+package gff;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import aligner.Alignment;
+import fileIO.ByteFile;
+import fileIO.ByteStreamWriter;
+import fileIO.FileFormat;
+import fileIO.ReadWrite;
+import prok.PGMTools;
+import prok.ProkObject;
+import shared.Parse;
+import shared.Parser;
+import shared.PreParser;
+import shared.ReadStats;
+import shared.Shared;
+import shared.Timer;
+import shared.Tools;
+import stream.ConcurrentReadOutputStream;
+import stream.Read;
+import stream.ReadInputStream;
+import structures.ByteBuilder;
+import tax.GiToTaxid;
+import tax.TaxClient;
+import tax.TaxTree;
+import template.Accumulator;
+import template.ThreadWaiter;
+
+public class CutGff implements Accumulator<CutGff.ProcessThread>  {
+	
+	/*--------------------------------------------------------------*/
+	/*----------------        Initialization        ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	/**
+	 * Code entrance from the command line.
+	 * @param args Command line arguments
+	 */
+	public static void main(String[] args){
+		//Start a timer immediately upon code entrance.
+		Timer t=new Timer();
+		
+		//Create an instance of this class
+		CutGff x=new CutGff(args);
+		
+		//Run the object
+		x.process(t);
+		
+		//Close the print stream if it was redirected
+		Shared.closeStream(x.outstream);
+	}
+	
+	/**
+	 * Constructor.
+	 * @param args Command line arguments
+	 */
+	public CutGff(String[] args){
+		{//Preparse block for help, config files, and outstream
+			PreParser pp=new PreParser(args, null/*getClass()*/, false);
+			args=pp.args;
+			outstream=pp.outstream;
+		}
+		
+		//Set shared static variables prior to parsing
+		ReadWrite.USE_PIGZ=ReadWrite.USE_UNPIGZ=true;
+		ReadWrite.MAX_ZIP_THREADS=Shared.threads();
+		
+		Shared.TRIM_READ_COMMENTS=Shared.TRIM_RNAME=true;
+		Read.TO_UPPER_CASE=true;
+		Read.VALIDATE_IN_CONSTRUCTOR=true;
+		GffLine.parseAttributes=true;
+		
+		{//Parse the arguments
+			final Parser parser=parse(args);
+			overwrite=parser.overwrite;
+			append=parser.append;
+
+			out=parser.out1;
+		}
+		
+		if(alignRibo){
+			//Load sequences
+			ProkObject.loadConsensusSequenceFromFile(false, false);
+		}
+		
+		fixExtensions(); //Add or remove .gz or .bz2 as needed
+		checkFileExistence(); //Ensure files can be read and written
+		checkStatics(); //Adjust file-related static fields as needed for this program
+
+		ffout=FileFormat.testOutput(out, FileFormat.PGM, null, true, overwrite, append, false);
+	}
+	
+	/*--------------------------------------------------------------*/
+	/*----------------    Initialization Helpers    ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	/** Parse arguments from the command line */
+	private Parser parse(String[] args){
+		
+		Parser parser=new Parser();
+		parser.overwrite=overwrite;
+		for(int i=0; i<args.length; i++){
+			String arg=args[i];
+			String[] split=arg.split("=");
+			String a=split[0].toLowerCase();
+			String b=split.length>1 ? split[1] : null;
+			if(b!=null && b.equalsIgnoreCase("null")){b=null;}
+
+//			outstream.println(arg+", "+a+", "+b);
+			if(PGMTools.parseStatic(arg, a, b)){
+				//do nothing
+			}else if(a.equals("in") || a.equals("infna") || a.equals("fnain") || a.equals("fna") || a.equals("ref")){
+				assert(b!=null);
+				Tools.addFiles(b, fnaList);
+			}else if(a.equals("gff") || a.equals("ingff") || a.equals("gffin")){
+				assert(b!=null);
+				Tools.addFiles(b, gffList);
+			}else if(a.equals("verbose")){
+				verbose=Parse.parseBoolean(b);
+				ReadWrite.verbose=verbose;
+			}else if(a.equals("alignribo") || a.equals("align")){
+				alignRibo=Parse.parseBoolean(b);
+			}else if(a.equals("adjustendpoints")){
+				adjustEndpoints=Parse.parseBoolean(b);
+			}else if(a.equalsIgnoreCase("slop16s") || a.equalsIgnoreCase("16sslop") || a.equalsIgnoreCase("ssuslop")){
+				ssuSlop=Integer.parseInt(b);
+			}else if(a.equalsIgnoreCase("slop23s") || a.equalsIgnoreCase("23sslop") || a.equalsIgnoreCase("lsuslop")){
+				lsuSlop=Integer.parseInt(b);
+			}else if(a.equalsIgnoreCase("maxns") || a.equalsIgnoreCase("maxundefined")){
+				maxNs=Integer.parseInt(b);
+			}else if(a.equalsIgnoreCase("maxnrate") || a.equalsIgnoreCase("maxnfraction")){
+				maxNFraction=Integer.parseInt(b);
+			}else if(a.equals("invert")){
+				invert=Parse.parseBoolean(b);
+			}else if(a.equals("type") || a.equals("types")){
+				types=b;
+			}else if(a.equals("attributes") || a.equals("requiredattributes")){
+				requiredAttributes=b.split(",");
+			}else if(a.equals("banattributes") || a.equals("bannedattributes")){
+				bannedAttributes=b.split(",");
+			}else if(a.equals("banpartial")){
+				banPartial=Parse.parseBoolean(b);
+			}
+			
+			else if(a.equalsIgnoreCase("renameByTaxID")){
+				renameByTaxID=Parse.parseBoolean(b);
+			}else if(a.equals("taxmode")){
+				if("accession".equalsIgnoreCase(b)){
+					taxMode=ACCESSION_MODE;
+				}else if("header".equalsIgnoreCase(b)){
+					taxMode=HEADER_MODE;
+				}else if("gi".equalsIgnoreCase(b)){
+					taxMode=GI_MODE;
+				}else if("taxid".equalsIgnoreCase(b)){
+					taxMode=TAXID_MODE;
+				}else{
+					assert(false) : "Bad tax mode: "+b;
+				}
+			}else if(a.equals("requirepresent")){
+				requirePresent=Parse.parseBoolean(b);
+			}else if(a.equalsIgnoreCase("onePerFile")){
+				onePerFile=Parse.parseBoolean(b);
+			}else if(a.equalsIgnoreCase("pickBest") || a.equalsIgnoreCase("findBest") || a.equalsIgnoreCase("keepBest")){
+				pickBest=Parse.parseBoolean(b);
+			}
+			
+			else if(a.equals("minlen")){
+				minLen=Integer.parseInt(b);
+			}else if(a.equals("maxlen")){
+				maxLen=Integer.parseInt(b);
+			}
+			
+			else if(ProkObject.parse(arg, a, b)){
+				//do nothing
+			}else if(parser.parse(arg, a, b)){
+				//do nothing
+			}else if(arg.indexOf('=')<0 && new File(arg).exists() && FileFormat.isFastaFile(arg)){
+				fnaList.add(arg);
+			}else{
+				outstream.println("Unknown parameter "+args[i]);
+				assert(false) : "Unknown parameter "+args[i];
+				//				throw new RuntimeException("Unknown parameter "+args[i]);
+			}
+		}
+
+		ArrayList<String> banned=new ArrayList<String>();
+		if(banPartial){banned.add("partial=true");}
+		if(bannedAttributes!=null){
+			for(String s : bannedAttributes){banned.add(s);}
+		}
+		bannedAttributes=banned.isEmpty() ? null : banned.toArray(new String[0]);
+		
+		if(gffList.isEmpty()){
+			for(String s : fnaList){
+				String prefix=ReadWrite.stripExtension(s);
+				String gff=prefix+".gff";
+				File f=new File(gff);
+				if(!f.exists()){
+					String gz=gff+".gz";
+					f=new File(gz);
+					assert(f.exists() && f.canRead()) : "Can't read file "+gff;
+					gff=gz;
+				}
+				gffList.add(gff);
+			}
+		}
+		assert(gffList.size()==fnaList.size()) : "Number of fna and gff files do not match: "+fnaList.size()+", "+gffList.size();
+		return parser;
+	}
+	
+	/** Add or remove .gz or .bz2 as needed */
+	private void fixExtensions(){
+		fnaList=Tools.fixExtension(fnaList);
+		gffList=Tools.fixExtension(gffList);
+		if(fnaList.isEmpty()){throw new RuntimeException("Error - at least one input file is required.");}
+	}
+	
+	/** Ensure files can be read and written */
+	private void checkFileExistence(){
+		//Ensure output files can be written
+		if(!Tools.testOutputFiles(overwrite, append, false, out)){
+			outstream.println((out==null)+", "+out);
+			throw new RuntimeException("\n\noverwrite="+overwrite+"; Can't write to output file "+out+"\n");
+		}
+		
+		//Ensure input files can be read
+		ArrayList<String> foo=new ArrayList<String>();
+		foo.addAll(fnaList);
+		foo.addAll(gffList);
+		if(!Tools.testInputFiles(false, true, foo.toArray(new String[0]))){
+			throw new RuntimeException("\nCan't read some input files.\n");  
+		}
+		
+		//Ensure that no file was specified multiple times
+		foo.add(out);
+		if(!Tools.testForDuplicateFiles(true, foo.toArray(new String[0]))){
+			throw new RuntimeException("\nSome file names were specified multiple times.\n");
+		}
+	}
+	
+	/** Adjust file-related static fields as needed for this program */
+	private static void checkStatics(){
+		//Adjust the number of threads for input file reading
+		if(!ByteFile.FORCE_MODE_BF1 && !ByteFile.FORCE_MODE_BF2 && Shared.threads()>2){
+			ByteFile.FORCE_MODE_BF2=true;
+		}
+	}
+	
+	/*--------------------------------------------------------------*/
+	/*----------------         Actual Code          ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	
+	
+	/*--------------------------------------------------------------*/
+	
+	public void process(Timer t){
+		if(Shared.threads()>2 && fnaList.size()>1){
+			processMT(t);
+		}else{
+			processST(t);
+		}
+	}
+	
+	public void processST(Timer t){
+		ByteStreamWriter bsw=(ffout==null ? null : new ByteStreamWriter(ffout));
+		if(bsw!=null){bsw.start();}
+		
+		for(int i=0; i<fnaList.size(); i++){
+			processFileST(fnaList.get(i), gffList.get(i), types, bsw);
+		}
+		
+		if(bsw!=null){bsw.poisonAndWait();}
+		t.stop();
+		if(ffout!=null){outstream.println("Wrote "+out);}
+		outstream.println(Tools.timeReadsBasesProcessed(t, readsProcessed, basesProcessed, 8));
+		outstream.println(Tools.readsBasesOut(readsProcessed, basesProcessed, readsOut, basesOut, 8, false));
+		if(alignRibo){
+			outstream.println(Tools.number("Flipped:           ", flipped.get(), 8));
+			outstream.println(Tools.number("Failed Alignment:  ", failed.get(), 8));
+		}
+	}
+	
+	public void processMT(Timer t){
+		
+		//Optionally create a read output stream
+		final ConcurrentReadOutputStream ros=makeCros();
+		
+		//Reset counters
+		readsProcessed=readsOut=0;
+		basesProcessed=basesOut=0;
+		
+		//Process the reads in separate threads
+		spawnThreads(ros);
+		
+		if(verbose){outstream.println("Finished; closing streams.");}
+		
+		//Write anything that was accumulated by ReadStats
+		errorState|=ReadStats.writeAll();
+		//Close the read streams
+		errorState|=ReadWrite.closeStream(ros);
+		
+		//Report timing and results
+		t.stop();
+		if(ffout!=null){outstream.println("Wrote "+out);}
+		outstream.println(Tools.timeReadsBasesProcessed(t, readsProcessed, basesProcessed, 8));
+		outstream.println(Tools.readsBasesOut(readsProcessed, basesProcessed, readsOut, basesOut, 8, false));
+		if(alignRibo){
+			outstream.println(Tools.number("Flipped:           ", flipped.get(), 8));
+			outstream.println(Tools.number("Failed Alignment:  ", failed.get(), 8));
+		}
+		
+		//Throw an exception of there was an error in a thread
+		if(errorState){
+			throw new RuntimeException(getClass().getName()+" terminated in an error state; the output may be corrupt.");
+		}
+	}
+	
+	/*--------------------------------------------------------------*/
+	
+	private boolean hasAttributes(GffLine gline){
+		int len=gline.length();
+		if(len<minLen || len>maxLen){return false;}
+		if(hasAttributes(gline, bannedAttributes)){return false;}
+		return requiredAttributes==null || hasAttributes(gline, requiredAttributes);
+	}
+	
+	private static boolean hasAttributes(GffLine gline, String[] attributes){
+		if(attributes==null){return false;}
+		for(String s : attributes){
+			if(gline.attributes.contains(s)){
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	private void processFileST(String fna, String gff, String types, ByteStreamWriter bsw){
+		ArrayList<Read> reads=processFile(fna, gff, types);
+		if(reads!=null){
+			for(Read r : reads){
+				if(bsw!=null){bsw.println(r);}
+			}
+		}
+	}
+	
+	private ArrayList<Read> processFile(String fna, String gff, String types){
+		ArrayList<GffLine> lines=GffLine.loadGffFile(gff, types, false);
+		
+		ArrayList<Read> list=ReadInputStream.toReads(fna, FileFormat.FA, -1);
+		HashMap<String, Read> map=new HashMap<String, Read>();
+		
+		for(Read r : list){
+			readsProcessed++;
+			basesProcessed+=r.length();
+			map.put(r.id, r);
+		}
+		
+		if(renameByTaxID){//Note this must be AFTER adding to the hashmap.
+			renameByTaxID(list);
+		}
+		
+		ArrayList<Read> outList=processLines(lines, map, invert);
+		
+		if(invert){
+			for(Read r : list){
+				readsOut++;
+				basesOut+=r.length();
+			}
+			return list;
+		}else{
+			if(outList!=null){
+				for(Read r : outList){
+					readsOut++;
+					basesOut+=r.length();
+				}
+			}
+			return outList;
+		}
+	}
+	
+	private void renameByTaxID(ArrayList<Read> list){
+		ByteBuilder bb=new ByteBuilder();
+		for(Read r : list){
+			if(bb.length>0){bb.comma();}
+			bb.append(taxMode==HEADER_MODE ? r.id : taxMode==ACCESSION_MODE ? parseAccession(r.id) : parseGi(r.id));
+		}
+		final int[] ids;
+		if(taxMode==ACCESSION_MODE){
+			ids=TaxClient.accessionToTaxidArray(bb.toString());
+		}else if(taxMode==GI_MODE){
+			ids=TaxClient.giToTaxidArray(bb.toString());
+		}else if(taxMode==HEADER_MODE){
+			ids=TaxClient.headerToTaxidArray(bb.toString());
+		}else if(taxMode==TAXID_MODE){
+			ids=parseTaxIds(list);
+		}else{
+			throw new RuntimeException("Bad mode: "+TAXID_MODE);
+		}
+		assert(ids.length==list.size()) : ids.length+", "+list.size();
+		for(int i=0; i<ids.length; i++){
+			Read r=list.get(i);
+			int id=ids[i];
+			
+			if(r.id!=null && r.id.startsWith("tid|")){
+				id=TaxTree.parseHeaderStatic(r.id);
+				r.obj=id;
+			}else {
+				assert(id>=0 || !requirePresent) : "Can't find taxID for header: "+id+", "+r.name();
+
+				r.obj=id;
+				r.id="tid|"+id+"|"+id;
+			}
+		}
+	}
+	
+	private int[] parseTaxIds(ArrayList<Read> list){
+		int[] ids=new int[list.size()];
+		for(int i=0; i<list.size(); i++){
+			Read r=list.get(i);
+			int x=GiToTaxid.parseTaxidNumber(r.id, '|');
+			ids[i]=x;
+		}
+		return ids;
+	}
+	
+	private String parseAccession(String id){
+		int dot=id.indexOf('.');
+		int space=id.indexOf(' ');
+//		assert(dot>0 && space>0) : "Unexpected header format: "+id+"\nTry header mode instead of accession mode.";
+		int limit=Tools.min((dot<0 ? id.length() : dot), (space<0 ? id.length() : space));
+		return id.substring(0, limit);
+	}
+	
+	private String parseGi(String id){
+		assert(id.startsWith("gi|"));
+		String[] split=id.split("|");
+		return split[1];
+	}
+	
+	private ArrayList<Read> processLines(ArrayList<GffLine> lines, HashMap<String, Read> map, boolean invertSelection){
+		ArrayList<Read> list=null; 
+		for(GffLine gline : lines){
+			if(hasAttributes(gline)){
+				Read scaf=map.get(gline.seqid);
+				assert(scaf!=null) : "Can't find "+gline.seqid+" in "+map.keySet();
+				
+				boolean pass=true;
+				Float identity=null;
+				if(alignRibo && gline.inbounds(scaf.length())){
+					int type=gline.prokType();
+					identity=align(gline, scaf.bases, type);
+					if(identity==null){pass=false;}
+				}
+				
+				if(pass){
+					final int start=gline.start-1;
+					final int stop=gline.stop-1;
+
+					if(invertSelection){
+						byte[] bases=scaf.bases;
+						for(int i=start; i<=stop; i++){
+							if(i>=0 && i<bases.length){
+								bases[i]='N';
+							}
+						}
+					}else{
+						if(start>=0 && stop<scaf.length()){
+							String id=gline.attributes;
+							if(renameByTaxID){
+								id="tid|"+scaf.obj+"|"+id;
+							}
+							Read r=new Read(Arrays.copyOfRange(scaf.bases, start, stop+1), null, id, 1);
+							r.obj=identity;
+							
+							assert(!r.containsLowercase()) : r.toFasta()+"\n"
+							+ "validated="+r.validated()+", scaf.validated="+scaf.validated()+", tuc="+Read.TO_UPPER_CASE+", vic="+Read.VALIDATE_IN_CONSTRUCTOR;
+							if(maxNs>=0 || maxNFraction>=0){
+								long allowed=Tools.min(maxNs>=0 ? maxNs : r.length(), (long)(r.length()*(maxNFraction>=0 ? maxNFraction : 1)));
+								if(r.countUndefined()>allowed){r=null;}
+							}
+							
+							if(r!=null){
+								if(gline.strand==1){r.reverseComplement();}
+								if(list==null){list=new ArrayList<Read>(8);}
+								list.add(r);
+								if(onePerFile && !pickBest){break;}
+							}
+						}
+					}
+				}
+			}
+		}
+		if(pickBest && list!=null && list.size()>1){
+			Read best=null;
+			float bestID=0;
+			for(Read r : list){
+				float identity=(r.obj==null ? 0.001f : (Float)r.obj);
+				if(identity>bestID){
+					bestID=identity;
+					best=r;
+				}
+			}
+			assert(best!=null);
+			list.clear();
+			list.add(best);
+		}
+		return list;
+	}
+	
+	private Float align(GffLine gline, byte[] scaf, int type){
+		Read[] consensusReads=ProkObject.consensusReads(type);
+		if(consensusReads==null || consensusReads.length==0){
+			assert(false) : type+"\n"+gline.toString();
+			return null;
+		}
+		byte[] universal=consensusReads[0].bases;
+		float minIdentity=ProkObject.minID(type)*ID_MULT;
+		if(universal==null){assert(false); return 1F;}
+		
+		int start=gline.start-1;
+		int stop=gline.stop-1;
+		assert(start<=stop) : start+", "+stop+", "+scaf.length;
+		assert(start>=0 && start<scaf.length) : start+", "+stop+", "+scaf.length;
+		assert(stop>=0 && stop<scaf.length) : start+", "+stop+", "+scaf.length;
+//		final int a=Tools.max(0, start-(adjustEndpoints ? 100 : 20));
+//		final int b=Tools.min(scaf.length-1, stop+(adjustEndpoints ? 100 : 20));
+		final int a=Tools.max(0, start);
+		final int b=Tools.min(scaf.length-1, stop);
+		
+		byte[] ref=Arrays.copyOfRange(scaf, a, b+1);
+		Read r=new Read(ref, null, 0);
+		if(gline.strand==GffLine.MINUS){r.reverseComplement();}
+
+		Alignment plus=new Alignment(r);
+		plus.align(universal);
+//		if(plus.id>=minIdentity){return true;}
+
+		r.reverseComplement();
+		Alignment minus=new Alignment(r);
+		minus.align(universal);
+		
+		Alignment best=null;
+		if(plus.id>=minus.id){
+			best=plus;
+//			System.err.println("Kept:    "+plus.id+" \t"+minus.id);
+		}else{
+			best=minus;
+			if(minus.id>=minIdentity){
+				if(verbose) {System.err.println("Flipped: "+plus.id+" \t"+minus.id+"");}
+				flipped.incrementAndGet();
+				gline.strand=Shared.MINUS;
+			}
+		}
+		if(best.id>=minIdentity){
+			return best.id;
+		}else{
+			if(verbose) {System.err.println("Failed alignment: "+plus.id+" \t"+minus.id);}
+			failed.incrementAndGet();
+			return null;
+		}
+//		
+//		int[] coords=KillSwitch.allocInt1D(2);
+//		float id1=align(universal, scaf, a, b, minIdentity, coords);
+//		final int rstart=coords[0], rstop=coords[1];
+//		//				assert(false) : rstart+", "+rstop+", "+(rstop-rstart+1)+", "+start+", "+stop;
+//		if(id1<minIdentity){
+//			System.err.println("Low identity: "+String.format("%.2s", 100*id1));
+//			return false;
+//		}
+//		if(adjustEndpoints){
+//			int slop=(flag==4 ? AnalyzeGenes.lsuSlop : AnalyzeGenes.ssuSlop);
+//			if(Tools.absdif(start, rstart)>slop){
+//				//						System.err.println("rstart:\t"+start+" -> "+rstart);
+//				start=rstart;
+//			}
+//			if(Tools.absdif(stop, rstop)>slop){
+//				//						System.err.println("rstop: \t"+stop+" -> "+rstop);
+//				stop=rstop;
+//			}
+//		}
+	}
+	
+	/*--------------------------------------------------------------*/
+	/*----------------       Thread Management      ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	private ConcurrentReadOutputStream makeCros(){
+		if(ffout==null){return null;}
+
+		//Select output buffer size based on whether it needs to be ordered
+		final int buff=(ordered ? Tools.mid(16, 128, (Shared.threads()*2)/3) : 8);
+
+		final ConcurrentReadOutputStream ros=ConcurrentReadOutputStream.getStream(ffout, null, buff, null, false);
+		ros.start(); //Start the stream
+		return ros;
+	}
+	
+	/** Spawn process threads */
+	private void spawnThreads(ConcurrentReadOutputStream ros){
+		//Do anything necessary prior to processing
+		
+		//Determine how many threads may be used
+		final int threads=Tools.min(Shared.threads(), fnaList.size());
+		
+		//Controls access to input files
+		AtomicInteger atom=new AtomicInteger(0);
+		
+		//Fill a list with ProcessThreads
+		ArrayList<ProcessThread> alpt=new ArrayList<ProcessThread>(threads);
+		for(int i=0; i<threads; i++){
+			alpt.add(new ProcessThread(atom, ros, i));
+		}
+		
+		//Start the threads and wait for them to finish
+		boolean success=ThreadWaiter.startAndWait(alpt, this);
+		errorState&=!success;
+		
+		//Do anything necessary after processing
+		
+	}
+	
+	@Override
+	public final void accumulate(ProcessThread pt){
+		readsProcessed+=pt.readsProcessedT;
+		basesProcessed+=pt.basesProcessedT;
+		readsOut+=pt.readsOutT;
+		basesOut+=pt.basesOutT;
+		errorState|=(!pt.success);
+	}
+	
+	@Override
+	public final boolean success(){return !errorState;}
+	
+	/*--------------------------------------------------------------*/
+	/*----------------         Inner Classes        ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	/** This class is static to prevent accidental writing to shared variables.
+	 * It is safe to remove the static modifier. */
+	class ProcessThread extends Thread {
+		
+		//Constructor
+		ProcessThread(final AtomicInteger atom_, ConcurrentReadOutputStream ros_, final int tid_){
+			atom=atom_;
+			ros=ros_;
+			tid=tid_;
+		}
+		
+		//Called by start()
+		@Override
+		public void run(){
+			//Do anything necessary prior to processing
+			
+			for(int fnum=atom.getAndIncrement(), lim=fnaList.size(); fnum<lim; fnum=atom.getAndIncrement()) {
+				String fna=fnaList.get(fnum);
+				String gff=gffList.get(fnum);
+				//Process the reads
+				ArrayList<Read> list=processFileT(fna, gff, types);
+				if(ros!=null){
+					if(list==null){list=dummy;}
+					ros.add(list, fnum);
+				}
+			}
+			
+			//Do anything necessary after processing
+			
+			//Indicate successful exit status
+			success=true;
+		}
+		
+		//Duplicated
+		private ArrayList<Read> processFileT(String fna, String gff, String types){
+			ArrayList<GffLine> lines=GffLine.loadGffFile(gff, types, false);
+			
+			ArrayList<Read> list=ReadInputStream.toReads(fna, FileFormat.FA, -1);
+			HashMap<String, Read> map=new HashMap<String, Read>();
+			
+			for(Read r : list){
+				readsProcessedT++;
+				basesProcessedT+=r.length();
+				map.put(r.id, r);
+			}
+			
+			if(renameByTaxID){//Note this must be AFTER adding to the hashmap.
+				renameByTaxID(list);
+			}
+//			assert(false) : renameByTaxID+", "+list.size();
+			
+			ArrayList<Read> outList=processLines(lines, map, invert);
+			
+			if(invert){
+				for(Read r : list){
+					readsOutT++;
+					basesOutT+=r.length();
+				}
+				return list;
+			}else{
+				if(outList!=null){
+					for(Read r : outList){
+						readsOutT++;
+						basesOutT+=r.length();
+					}
+				}
+				return outList;
+			}
+		}
+
+		/** Number of reads processed by this thread */
+		protected long readsProcessedT=0;
+		/** Number of bases processed by this thread */
+		protected long basesProcessedT=0;
+		
+		/** Number of reads retained by this thread */
+		protected long readsOutT=0;
+		/** Number of bases retained by this thread */
+		protected long basesOutT=0;
+		
+		/** True only if this thread has completed successfully */
+		boolean success=false;
+		
+		/** Shared output stream */
+		private final ConcurrentReadOutputStream ros;
+		/** Thread ID */
+		final int tid;
+		/** Next file ID */
+		final AtomicInteger atom;
+	}
+	
+	/*--------------------------------------------------------------*/
+	/*----------------            Fields            ----------------*/
+	/*--------------------------------------------------------------*/
+
+	private ArrayList<String> fnaList=new ArrayList<String>();
+	private ArrayList<String> gffList=new ArrayList<String>();
+	private String out=null;
+	private String types="CDS";
+	private boolean invert=false;
+	private boolean banPartial=true;
+	private int minLen=1;
+	private int maxLen=Integer.MAX_VALUE;
+
+	private String[] requiredAttributes;
+	private String[] bannedAttributes;
+	
+	/*--------------------------------------------------------------*/
+	
+	private long bytesOut=0;
+	private boolean renameByTaxID=false;
+	private int taxMode=ACCESSION_MODE;
+	private boolean requirePresent=false;
+	private boolean alignRibo=false;
+	private boolean adjustEndpoints=false;
+	private boolean onePerFile=false;
+	private boolean pickBest=false;
+	private int ssuSlop=999;
+	private int lsuSlop=999;
+	
+	private float ID_MULT=0.96f;
+	
+	private int maxNs=-1;
+	private double maxNFraction=-1;
+	
+	private static int ACCESSION_MODE=0, GI_MODE=1, HEADER_MODE=2, TAXID_MODE=3;
+	
+	/*--------------------------------------------------------------*/
+
+	/** Number of reads processed */
+	protected long readsProcessed=0;
+	/** Number of bases processed */
+	protected long basesProcessed=0;
+
+	/** Number of reads retained */
+	protected long readsOut=0;
+	/** Number of bases retained */
+	protected long basesOut=0;
+
+	protected AtomicLong flipped=new AtomicLong(0);
+	protected AtomicLong failed=new AtomicLong(0);
+
+	/** Quit after processing this many input reads; -1 means no limit */
+	private long maxReads=-1;
+	
+	/*--------------------------------------------------------------*/
+	/*----------------         Final Fields         ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	private final FileFormat ffout;
+	private final ArrayList<Read> dummy=new ArrayList<Read>();
+	
+	/*--------------------------------------------------------------*/
+	/*----------------        Common Fields         ----------------*/
+	/*--------------------------------------------------------------*/
+	
+	private PrintStream outstream=System.err;
+	public static boolean verbose=false;
+	public boolean errorState=false;
+	public boolean ordered=true;
+	private boolean overwrite=true;
+	private boolean append=false;
+	
+}