1 package tax;
2 
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.PrintStream;
7 import java.net.InetSocketAddress;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Collections;
11 import java.util.Date;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Map.Entry;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.atomic.AtomicInteger;
18 import java.util.concurrent.atomic.AtomicLong;
19 import java.util.concurrent.atomic.AtomicLongArray;
20 
21 import com.sun.net.httpserver.HttpExchange;
22 import com.sun.net.httpserver.HttpHandler;
23 import com.sun.net.httpserver.HttpServer;
24 import com.sun.net.httpserver.HttpsServer;
25 
26 import dna.Data;
27 import fileIO.ReadWrite;
28 import json.JsonObject;
29 import server.PercentEncoding;
30 import server.ServerTools;
31 import shared.KillSwitch;
32 import shared.Parse;
33 import shared.Parser;
34 import shared.PreParser;
35 import shared.Shared;
36 import shared.Timer;
37 import shared.Tools;
38 import sketch.CompareBuffer;
39 import sketch.Comparison;
40 import sketch.DisplayParams;
41 import sketch.Sketch;
42 import sketch.SketchMakerMini;
43 import sketch.SketchObject;
44 import sketch.SketchResults;
45 import sketch.SketchSearcher;
46 import sketch.SketchTool;
47 import sketch.Whitelist;
48 import stream.Read;
49 import structures.ByteBuilder;
50 import structures.IntList;
51 import structures.StringNum;
52 
53 /**
54  * Server for taxonomy or Sketch queries.
55  * @author Shijie Yao, Brian Bushnell
56  * @date Dec 13, 2016
57  *
58  */
59 public class TaxServer {
60 
61 	/*--------------------------------------------------------------*/
62 	/*----------------            Startup           ----------------*/
63 	/*--------------------------------------------------------------*/
64 
65 	/** Command line entrance */
main(String[] args)66 	public static void main(String[] args) throws Exception {
67 		Timer t=new Timer();
68 		@SuppressWarnings("unused")
69 		TaxServer ts=new TaxServer(args);
70 
71 		t.stop("Time: ");
72 
73 		System.err.println("Ready!");
74 
75 		//ts.begin();
76 	}
77 
78 	/** Constructor */
TaxServer(String[] args)79 	public TaxServer(String[] args) throws Exception {
80 
81 		{//Preparse block for help, config files, and outstream
82 			PreParser pp=new PreParser(args, getClass(), false);
83 			args=pp.args;
84 			outstream=pp.outstream;
85 		}
86 
87 		ReadWrite.USE_UNPIGZ=true;
88 		TaxFilter.printNodesAdded=false;
89 		TaxFilter.REQUIRE_PRESENT=false; //Due to missing entries in TaxDump.
90 		Read.JUNK_MODE=Read.FIX_JUNK;
91 		SketchObject.compareSelf=true;
92 
93 		int port_=3068; //Taxonomy server
94 		String killCode_=null;
95 		boolean allowRemoteFileAccess_=false;
96 		boolean allowLocalHost_=false;
97 		String addressPrefix_="128."; //LBL
98 		long defaultSketchReads=200000;
99 		boolean https=false;
100 
101 		int serverNum_=0;
102 		int serverCount_=1;
103 
104 		//Create a parser object
105 		Parser parser=new Parser();
106 
107 		//Parse each argument
108 		for(int i=0; i<args.length; i++){
109 			String arg=args[i];
110 
111 			//Break arguments into their constituent parts, in the form of "a=b"
112 			String[] split=arg.split("=");
113 			String a=split[0].toLowerCase();
114 			String b=split.length>1 ? split[1] : null;
115 
116 			if(a.equals("verbose")){
117 				verbose=Parse.parseBoolean(b);
118 			}else if(a.equals("verbose2")){
119 				verbose2=SketchObject.verbose2=Parse.parseBoolean(b);
120 			}else if(a.equals("html")){
121 				useHtml=Parse.parseBoolean(b);
122 			}else if(a.equals("https")){
123 				https=Parse.parseBoolean(b);
124 			}else if(a.equals("http")){
125 				https=!Parse.parseBoolean(b);
126 			}else if(a.equals("servers") || a.equals("numservers") || a.equals("servercount")){
127 				serverCount_=Integer.parseInt(b);
128 				assert(serverCount_>0) : arg;
129 			}else if(a.equals("servernum")){
130 				serverNum_=Integer.parseInt(b);
131 				assert(serverNum_>=0) : arg;
132 			}else if(a.startsWith("slave") && Tools.isDigit(a.charAt(a.length()-1))){
133 				int num=Integer.parseInt(a.substring(5));
134 				if(slaveAddress==null){slaveAddress=new ArrayList<String>(serverCount_);}
135 				while(slaveAddress.size()<=num){slaveAddress.add(null);}
136 				slaveAddress.set(num, b);
137 			}else if(a.equals("table") || a.equals("gi") || a.equals("gitable")){
138 				giTableFile=b;
139 			}else if(a.equals("tree") || a.equals("taxtree")){
140 				taxTreeFile=b;
141 			}else if(a.equals("accession")){
142 				accessionFile=b;
143 			}else if(a.equals("pattern")){
144 				patternFile=b;
145 			}else if(a.equals("size") || a.equals("sizefile")){
146 				sizeFile=b;
147 			}else if(a.equalsIgnoreCase("img")){
148 				imgFile=b;
149 			}else if(a.equals("domain")){
150 				domain=b;
151 				while(domain!=null && domain.endsWith("/")){domain=domain.substring(0, domain.length()-1);}
152 			}else if(a.equals("port")){
153 				port_=Integer.parseInt(b);
154 			}else if(a.equals("kill") || a.equals("killcode")){
155 				killCode_=b;
156 			}else if(a.equals("oldcode")){
157 				oldKillCode=b;
158 			}else if(a.equals("oldaddress")){
159 				oldAddress=b;
160 			}else if(a.equals("sketchonly")){
161 				sketchOnly=Parse.parseBoolean(b);
162 			}else if(a.equals("sketchreads")){
163 				defaultSketchReads=Parse.parseKMG(b);
164 			}else if(a.equals("handlerthreads")){
165 				handlerThreads=Integer.parseInt(b);
166 			}else if(a.equals("sketchthreads") || a.equals("sketchcomparethreads")){
167 				maxConcurrentSketchCompareThreads=Integer.parseInt(b);
168 			}else if(a.equals("sketchloadthreads")){
169 				maxConcurrentSketchLoadThreads=Integer.parseInt(b);
170 			}else if(a.equals("hashnames")){
171 				hashNames=Parse.parseBoolean(b);
172 			}else if(a.equals("hashdotformat")){
173 				hashDotFormat=Parse.parseBoolean(b);
174 			}else if(a.equals("printip")){
175 				printIP=Parse.parseBoolean(b);
176 			}else if(a.equals("printheaders")){
177 				printHeaders=Parse.parseBoolean(b);
178 			}else if(a.equals("countqueries")){
179 				countQueries=Parse.parseBoolean(b);
180 			}else if(a.equals("clear") || a.equals("clearmem")){
181 				clearMem=Parse.parseBoolean(b);
182 			}else if(a.equals("dbname")){
183 				SketchObject.defaultParams.dbName=b;
184 			}else if(a.equals("allowremotefileaccess")){
185 				allowRemoteFileAccess_=Parse.parseBoolean(b);
186 			}else if(a.equals("allowlocalhost")){
187 				allowLocalHost_=Parse.parseBoolean(b);
188 			}else if(a.equals("addressprefix")){
189 				addressPrefix_=b;
190 			}else if(a.equals("maxpigzprocesses")){
191 				AccessionToTaxid.maxPigzProcesses=Integer.parseInt(b);
192 			}else if(a.equals("path") || a.equals("treepath") || a.equals("basepath")){
193 				basePath=b;
194 			}else if(a.equalsIgnoreCase("prealloc")){
195 				if(b==null || Character.isLetter(b.charAt(0))){
196 					if(Parse.parseBoolean(b)){
197 						prealloc=0.78f;
198 					}else{
199 						prealloc=0;
200 					}
201 				}else{
202 					prealloc=Float.parseFloat(b);
203 				}
204 				SketchObject.prealloc=prealloc;
205 			}else if(searcher.parse(arg, a, b, true)){
206 				//do nothing
207 			}else if(parser.parse(arg, a, b)){//Parse standard flags in the parser
208 				//do nothing
209 			}else{
210 				throw new RuntimeException(arg);
211 			}
212 		}
213 		if("auto".equalsIgnoreCase(imgFile)){imgFile=TaxTree.defaultImgFile();}
214 		if("auto".equalsIgnoreCase(taxTreeFile)){taxTreeFile=TaxTree.defaultTreeFile();}
215 		if("auto".equalsIgnoreCase(giTableFile)){giTableFile=TaxTree.defaultTableFile();}
216 		if("auto".equalsIgnoreCase(accessionFile)){accessionFile=TaxTree.defaultAccessionFile();}
217 		if("auto".equalsIgnoreCase(patternFile)){patternFile=TaxTree.defaultPatternFile();}
218 		if("auto".equalsIgnoreCase(sizeFile)){sizeFile=TaxTree.defaultSizeFile();}
219 
220 		serverNum=AccessionToTaxid.serverNum=serverNum_;
221 		serverCount=AccessionToTaxid.serverCount=serverCount_;
222 		distributed=AccessionToTaxid.distributed=serverCount>1;
223 		assert(serverNum<serverCount && serverNum>=0);
224 		if(distributed && serverNum==0){
225 			assert(slaveAddress!=null);
226 			assert(slaveAddress.size()==serverCount);
227 			for(int i=1; i<slaveAddress.size(); i++){
228 				assert(slaveAddress.get(i)!=null);
229 			}
230 		}
231 
232 		maxConcurrentSketchCompareThreads=Tools.mid(1, maxConcurrentSketchCompareThreads, Shared.threads());
233 		maxConcurrentSketchLoadThreads=Tools.mid(1, maxConcurrentSketchLoadThreads, Shared.threads());
234 		assert(maxConcurrentSketchCompareThreads>=1);
235 		assert(maxConcurrentSketchLoadThreads>=1);
236 
237 		if(basePath==null || basePath.trim().length()==0){basePath="";}
238 		else{
239 			basePath=basePath.trim().replace('\\', '/').replaceAll("/+", "/");
240 			if(!basePath.endsWith("/")){basePath=basePath+"/";}
241 		}
242 
243 		//Adjust SketchSearch rcomp and amino flags
244 		SketchObject.postParse();
245 
246 		if(sketchOnly){
247 //			hashNames=false;
248 			giTableFile=null;
249 			accessionFile=null;
250 			imgFile=null;
251 			patternFile=null;
252 		}
253 
254 		port=port_;
255 		killCode=killCode_;
256 		allowRemoteFileAccess=allowRemoteFileAccess_;
257 		allowLocalHost=allowLocalHost_;
258 		addressPrefix=addressPrefix_;
259 
260 		//Fill some data objects
261 		USAGE=makeUsagePrefix();
262 		rawHtml=(useHtml ? loadRawHtml() : null);
263 		typeMap=makeTypeMap();
264 		commonMap=makeCommonMap();
265 
266 		//Load the GI table
267 		if(giTableFile!=null){
268 			outstream.println("Loading gi table.");
269 			GiToTaxid.initialize(giTableFile);
270 		}
271 
272 		//Load the taxTree
273 		if(taxTreeFile!=null){
274 			tree=TaxTree.loadTaxTree(taxTreeFile, outstream, hashNames, hashDotFormat);
275 			if(hashNames){tree.hashChildren();}
276 			assert(tree.nameMap!=null || sketchOnly);
277 		}else{//The tree is required
278 			tree=null;
279 			throw new RuntimeException("No tree specified.");
280 		}
281 		//Set a default taxtree for sketch-related usage
282 		SketchObject.taxtree=tree;
283 
284 		if(sizeFile!=null){
285 			Timer t=new Timer();
286 			outstream.println("Loading size file.");
287 			tree.loadSizeFile(sizeFile);
288 			t.stopAndPrint();
289 		}
290 
291 		if(imgFile!=null){
292 			TaxTree.loadIMG(imgFile, false, outstream);
293 		}
294 
295 		if(patternFile!=null){
296 			Timer t=new Timer();
297 			AnalyzeAccession.loadCodeMap(patternFile);
298 			outstream.println("Loading pattern table.");
299 			t.stopAndPrint();
300 		}
301 
302 		//Load accession files
303 		if(accessionFile!=null){
304 			Timer t=new Timer();
305 			AccessionToTaxid.tree=tree;
306 			AccessionToTaxid.prealloc=prealloc;
307 			outstream.println("Loading accession table.");
308 			AccessionToTaxid.load(accessionFile);
309 			t.stopAndPrint();
310 //			if(searcher.refFiles.isEmpty()){System.gc();}
311 		}
312 
313 //		assert(false) : searcher.refFileCount();
314 
315 		//Load reference sketches
316 		hasSketches=searcher.refFileCount()>0;
317 		if(hasSketches){
318 			outstream.println("Loading sketches.");
319 			Timer t=new Timer();
320 			searcher.loadReferences(SketchObject.PER_TAXA, SketchObject.defaultParams);
321 			t.stopAndPrint();
322 //			System.gc();
323 		}
324 
325 		SketchObject.allowMultithreadedFastq=(maxConcurrentSketchLoadThreads>1);
326 		SketchObject.defaultParams.maxReads=defaultSketchReads;
327 		ReadWrite.USE_UNPIGZ=false;
328 //		ReadWrite.USE_UNBGZIP=false;
329 
330 		if(clearMem){
331 			System.err.println("Clearing memory.");
332 			System.gc();
333 			Shared.printMemory();
334 		}
335 
336 		//If there is a kill code, kill the old instance
337 		if(oldKillCode!=null && oldAddress!=null){
338 			outstream.println("Killing old instance.");
339 			killOldInstance();
340 		}
341 
342 		//Wait for server initialization
343 		httpServer=initializeServer(1000, 8, https);
344 		assert(httpServer!=null);
345 
346 		//Initialize handlers
347 		if(!sketchOnly){
348 			httpServer.createContext("/", new TaxHandler(false));
349 			httpServer.createContext("/tax", new TaxHandler(false));
350 			httpServer.createContext("/stax", new TaxHandler(true));
351 			httpServer.createContext("/simpletax", new TaxHandler(true));
352 		}else{
353 			httpServer.createContext("/", new SketchHandler());
354 		}
355 		httpServer.createContext("/sketch", new SketchHandler());
356 		if(killCode!=null){
357 			httpServer.createContext("/kill", new KillHandler());
358 		}
359 
360 		httpServer.createContext("/help", new HelpHandler());
361 		httpServer.createContext("/usage", new HelpHandler());
362 		httpServer.createContext("/stats", new StatsHandler());
363 		httpServer.createContext("/favicon.ico", new IconHandler());
364 
365 		handlerThreads=handlerThreads>0 ? handlerThreads : Tools.max(2, Shared.threads());
366 		httpServer.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(handlerThreads)); // Creates a multithreaded executor
367 //		httpServer.setExecutor(java.util.concurrent.Executors.newCachedThreadPool()); // Creates a multithreaded executor
368 //		httpServer.setExecutor(null); // Creates a singlethreaded executor
369 
370 		//Start the server
371 		httpServer.start();
372 	}
373 
374 	/** Kill a prior server instance */
killOldInstance()375 	private void killOldInstance(){
376 		StringNum result=null;
377 		try {
378 			result=ServerTools.sendAndReceive(oldKillCode.getBytes(), oldAddress);
379 		} catch (Exception e) {
380 			// TODO Auto-generated catch block
381 			if(e!=null){e.printStackTrace();}
382 			System.err.println("\nException suppressed; continuing.\n");
383 			return;
384 		}
385 		if(result==null || result.s==null || !"Success.".equals(result.s)){
386 //			KillSwitch.kill("Bad kill result: "+result+"\nQuitting.\n");
387 			System.err.println("Bad kill result: "+result+"\nContinuing.\n");
388 		}
389 		ServerTools.pause(1000);
390 	}
391 
392 	/** Iterative wait for server initialization */
initializeServer(int millis0, int iterations, boolean https)393 	private HttpServer initializeServer(int millis0, int iterations, boolean https){
394 		HttpServer server=null;
395 		InetSocketAddress isa=new InetSocketAddress(port);
396 		Exception ee=null;
397 		for(int i=0, millis=millis0; i<iterations && server==null; i++){
398 			try {
399 				if(https){
400 					server=HttpsServer.create(isa, 0);
401 				}else{
402 					server=HttpServer.create(isa, 0);
403 				}
404 			} catch (java.net.BindException e) {//Expected
405 				System.err.println(e);
406 				System.err.println("\nWaiting "+millis+" ms");
407 				ee=e;
408 				ServerTools.pause(millis);
409 				millis=millis*2;
410 			} catch (IOException e) {//Not sure when this would occur...  it would be unexpected
411 				System.err.println(e);
412 				System.err.println("\nWaiting "+millis+" ms");
413 				ee=e;
414 				ServerTools.pause(millis);
415 				millis=millis*2;
416 			}
417 		}
418 		if(server==null){throw new RuntimeException(ee);}
419 		return server;
420 	}
421 
returnUsage(long startTime, HttpExchange t)422 	public void returnUsage(long startTime, HttpExchange t){
423 		if(useHtml){
424 			returnUsageHtml(startTime, t);
425 			return;
426 		}
427 		if(logUsage){System.err.println("usage");}
428 //		String usage=USAGE(USAGE);
429 		bytesOut.addAndGet(USAGE.length());
430 		ServerTools.reply(USAGE, "text/plain", t, verbose2, 200, true);
431 		final long stopTime=System.nanoTime();
432 		final long elapsed=stopTime-startTime;
433 		timeMeasurementsUsage.incrementAndGet();
434 		elapsedTimeUsage.addAndGet(elapsed);
435 		lastTimeUsage.set(elapsed);
436 	}
437 
returnUsageHtml(long startTime, HttpExchange t)438 	public void returnUsageHtml(long startTime, HttpExchange t){
439 		if(logUsage){System.err.println("usage");}
440 		String s=makeUsageHtml();
441 		bytesOut.addAndGet(s.length());
442 		ServerTools.reply(s, "html", t, verbose2, 200, true);
443 		final long stopTime=System.nanoTime();
444 		final long elapsed=stopTime-startTime;
445 		timeMeasurementsUsage.incrementAndGet();
446 		elapsedTimeUsage.addAndGet(elapsed);
447 		lastTimeUsage.set(elapsed);
448 	}
449 
returnStats(long startTime, HttpExchange t)450 	public void returnStats(long startTime, HttpExchange t){
451 		if(logUsage){System.err.println("stats");}
452 		String stats=makeStats();
453 		bytesOut.addAndGet(stats.length());
454 		ServerTools.reply(stats, "text/plain", t, verbose2, 200, true);
455 		final long stopTime=System.nanoTime();
456 		final long elapsed=stopTime-startTime;
457 		timeMeasurementsUsage.incrementAndGet();
458 		elapsedTimeUsage.addAndGet(elapsed);
459 		lastTimeUsage.set(elapsed);
460 	}
461 
462 	/*--------------------------------------------------------------*/
463 	/*----------------           Handlers           ----------------*/
464 	/*--------------------------------------------------------------*/
465 
466 	/** Handles queries for favicon.ico */
467 	class IconHandler implements HttpHandler {
468 
469 		@Override
handle(HttpExchange t)470 		public void handle(HttpExchange t) throws IOException {
471 			if(verbose2){System.err.println("Icon handler");}
472 			iconQueries.incrementAndGet();
473 			ServerTools.reply(favIcon, "image/x-icon", t, verbose2, 200, true);
474 		}
475 
476 	}
477 
478 	/*--------------------------------------------------------------*/
479 
480 	/** Handles queries that fall through other handlers */
481 	class HelpHandler implements HttpHandler {
482 
483 		@Override
handle(HttpExchange t)484 		public void handle(HttpExchange t) throws IOException {
485 			if(verbose2){System.err.println("Help handler");}
486 			final long startTime=System.nanoTime();
487 			returnUsage(startTime, t);
488 		}
489 
490 	}
491 
492 	/*--------------------------------------------------------------*/
493 
494 	/** Handles queries that fall through other handlers */
495 	class StatsHandler implements HttpHandler {
496 
497 		@Override
handle(HttpExchange t)498 		public void handle(HttpExchange t) throws IOException {
499 			if(verbose2){System.err.println("Http handler");}
500 			final long startTime=System.nanoTime();
501 			returnStats(startTime, t);
502 		}
503 
504 	}
505 
506 	/*--------------------------------------------------------------*/
507 
508 	/** Handles requests to kill the server */
509 	class KillHandler implements HttpHandler {
510 
511 		@Override
handle(HttpExchange t)512 		public void handle(HttpExchange t) throws IOException {
513 			if(verbose2){System.err.println("Kill handler");}
514 
515 			//Parse the query from the URL
516 			String rparam=getRParam(t, false);
517 			InetSocketAddress remote=t.getRemoteAddress();
518 
519 			if(testCode(t, rparam)){
520 				ServerTools.reply("Success.", "text/plain", t, verbose2, 200, true);
521 				System.err.println("Killed by remote address "+remote);
522 				//TODO: Perhaps try to close open resources such as the server
523 				KillSwitch.killSilent();
524 			}
525 
526 			if(verbose){System.err.println("Bad kill from address "+remote);}
527 			ServerTools.reply(BAD_CODE, "text/plain", t, verbose2, 403, true);
528 		}
529 
530 		/** Determines whether kill code was correct */
testCode(HttpExchange t, String rparam)531 		private boolean testCode(HttpExchange t, String rparam){
532 			String[] params = rparam.split("/");
533 			if(verbose2){System.err.println(Arrays.toString(params));}
534 
535 			if(killCode!=null){
536 				if(params.length>1){//URL mode
537 					return (params[1].equals(killCode));
538 				}else{//Body mode
539 					try {
540 						String code=ServerTools.receive(t);
541 						return (code!=null && code.equals(killCode));
542 					} catch (Exception e) {
543 						// TODO Auto-generated catch block
544 						e.printStackTrace();
545 					}
546 				}
547 			}
548 			return false;
549 		}
550 	}
551 
552 	/*--------------------------------------------------------------*/
553 
554 	/** Listens for sketch comparison requests */
555 	class SketchHandler implements HttpHandler {
556 
557 		@Override
handle(HttpExchange t)558 		public void handle(HttpExchange t) throws IOException {
559 			if(verbose2){outstream.println("Got a request.");}
560 			SketchInstance si=new SketchInstance(t);
561 			if(verbose2){outstream.println("Made si.");}
562 			si.handleInner();
563 			if(verbose2){outstream.println("Done.");}
564 		}
565 
loadSketchesFromBody(String body)566 		private ArrayList<Sketch> loadSketchesFromBody(String body){
567 			//List of query sketches
568 			ArrayList<Sketch> sketches=null;
569 
570 			if(body!=null && body.length()>0){
571 				sketches=searcher.loadSketchesFromString(body);
572 				if(Whitelist.exists()){
573 					for(Sketch sk : sketches){
574 						Whitelist.apply(sk);
575 					}
576 				}
577 			}
578 			return sketches;
579 		}
580 
loadSketchesFromFile(String fname, DisplayParams params)581 		private ArrayList<Sketch> loadSketchesFromFile(String fname, DisplayParams params){
582 			//List of query sketches
583 			ArrayList<Sketch> sketches=null;
584 
585 			SketchTool tool=searcher.tool;
586 			if(tool.minKeyOccuranceCount!=params.minKeyOccuranceCount || params.trackCounts()){
587 				tool=new SketchTool(SketchObject.targetSketchSize, params);
588 			}
589 
590 			if(verbose2){System.err.println("Loading sketches from file "+fname);}
591 			sketches=tool.loadSketchesFromFile(fname, (SketchMakerMini)null, maxConcurrentSketchLoadThreads, params.maxReads, params.mode, params, true);
592 			if(verbose2){System.err.println("Loaded "+(sketches==null ? "null" : sketches.size())+" sketches from file "+fname);}
593 			return sketches;
594 		}
595 
596 		//Created in handle()
597 		private class SketchInstance {
598 
SketchInstance(HttpExchange t_)599 			SketchInstance(HttpExchange t_){
600 				t=t_;
601 				instanceStartTime=System.nanoTime();
602 			}
603 
handleInner()604 			void handleInner(){
605 
606 				if(!hasSketches){
607 					if(verbose2){System.err.println("No sketches.");}
608 					ServerTools.reply("\nERROR: This server has no sketches loaded.\n"
609 							+ "Please download the latest BBTools version to use SendSketch.\n", "text/plain", t, verbose2, 400, true);
610 					return;
611 				}
612 
613 				String rparam=parseRparamSketch(t);
614 				if(verbose2){System.err.println("Parsed rparam.");}
615 				if(rparam==null){
616 					returnUsage(instanceStartTime, t);
617 					return;
618 				}
619 				final boolean internal=incrementQueries(t, fileMode, refMode, false, false, false, false, false, false, false, false, -1);
620 				if(verbose2){System.err.println("Incremented queries rparam.");}
621 
622 				if(verbose2){System.err.println(rparam);}
623 				if(verbose2){System.err.println("fileMode="+fileMode+", refMode="+refMode);}
624 
625 				if(fileMode && !internal && !allowRemoteFileAccess){
626 					if(verbose){System.err.println("Illegal file query from "+ServerTools.getClientAddress(t));}
627 					malformedQueries.incrementAndGet();
628 //					if(verbose){System.err.println("test1");}
629 //					String body=getBody(t);//123
630 					ServerTools.reply("\nERROR: This server does not allow remote file access. "
631 							+ "You may only use the 'local' flag from with the local intranet.\n", "text/plain", t, verbose2, 400, true);
632 //					if(verbose){System.err.println("test2");}
633 					return;
634 				}
635 
636 				String body=getBody(t);
637 				if(verbose2){System.err.println("Got body.");}
638 
639 				if(body!=null){bytesIn.addAndGet(body.length());}
640 
641 				if(verbose2){System.err.println("Found body: "+body);}
642 				if(body!=null && body.length()>0){
643 					if((fileMode || refMode) && !body.startsWith("##")){
644 						body="##"+body;
645 					}
646 					try {
647 						params=params.parseDoubleHeader(body);
648 						if(verbose2){System.err.println("Passed parse params.");}
649 
650 					} catch (Throwable e) {
651 						String s=Tools.toString(e);
652 						ServerTools.reply("\nERROR: \n"+ s,
653 								"text/plain", t, verbose2, 400, true);
654 						return;
655 					}
656 					if(!params.compatible()){
657 						ServerTools.reply("\nERROR: The sketch is not compatible with this server.\n"
658 								+ "Server settings: k="+SketchObject.k+(SketchObject.k2>0 ? ","+SketchObject.k2 : "")
659 								+" amino="+SketchObject.amino+" hash_version="+SketchObject.HASH_VERSION+"\n"
660 								+ "You may need to download a newer version of BBTools; this server is running version "+Shared.BBMAP_VERSION_STRING,
661 								"text/plain", t, verbose2, 400, true);
662 						return;
663 					}
664 				}
665 				if(params.trackCounts()){
666 					depthQueries.incrementAndGet();
667 				}
668 
669 				if(verbose2){System.err.println("Parsed params: "+params.toString());}
670 
671 				//List of query sketches
672 				ArrayList<Sketch> sketches;
673 
674 				if(fileMode){
675 					File f=new File(rparam);
676 					if(!f.exists() && !rparam.startsWith("/")){
677 						String temp="/"+rparam;
678 						f=new File(temp);
679 						if(f.exists()){rparam=temp;}
680 					}
681 //					if(f.exists()){
682 //						if(f.length()>100000000L){
683 //							if(params.reads<0){
684 //								params.reads=200000;//Cap default number of reads at 200000
685 //							}
686 //						}
687 //					}
688 					sketches=loadSketchesFromFile(rparam, params);
689 				}else if(refMode){
690 					String[] split=rparam.split(",");
691 					sketches=new ArrayList<Sketch>(split.length);
692 					for(String s : split){
693 						Sketch sk=findRefSketch(s);
694 						if(sk!=null){sketches.add(sk);}
695 					}
696 				}else{
697 					sketches=loadSketchesFromBody(body);
698 				}
699 				if(verbose2){System.err.println("Loaded "+sketches.size()+" sketches.");}
700 
701 				final int numSketches=sketches==null ? 0 : sketches.size();
702 				if(params.chunkNum<0){
703 					if(numSketches<2){
704 						unknownChunkSingle.incrementAndGet();
705 					}else{
706 						unknownChunkMulti.incrementAndGet();
707 					}
708 				}else if(params.chunkNum==0){
709 					if(numSketches<2){
710 						firstChunkSingle.incrementAndGet();
711 					}else{
712 						firstChunkMulti.incrementAndGet();
713 					}
714 				}else{
715 					if(numSketches<2){
716 						nthChunkSingle.incrementAndGet();
717 					}else{
718 						nthChunkMulti.incrementAndGet();
719 					}
720 				}
721 				bulkCount.addAndGet(numSketches);
722 
723 				if(params.inputVersion==null){params.inputVersion="unknown";}
724 				synchronized(versionMap){
725 					StringNum sn=versionMap.get(params.inputVersion);
726 					if(sn==null){versionMap.put(params.inputVersion, new StringNum(params.inputVersion, 1));}
727 					else{sn.increment();}
728 				}
729 
730 				String response=null;
731 				if(sketches==null || sketches.isEmpty()){
732 					malformedQueries.incrementAndGet();
733 					response="Error.";
734 					if(verbose){
735 						StringBuilder sb=new StringBuilder();
736 						sb.append("Malformed query from ").append(ServerTools.getClientAddress(t)).append(". body:");
737 						if(body==null){
738 							sb.append(" null");
739 						}else{
740 							String[] split = body.split("\n");
741 							sb.append(" ").append(split.length).append(" lines total, displaying ").append(Tools.min(3, split.length)).append('.');
742 							for(int i=0; i<3 && i<split.length; i++){
743 								String s=split[i];
744 								int len=s.length();
745 								if(s.length()>1000){s=s.substring(0, 1000)+" [truncated, "+len+" total]";}
746 								sb.append('\n');
747 								sb.append(s);
748 							}
749 						}
750 						System.err.println(sb);
751 					}
752 				}else{
753 					if(verbose2){
754 						System.err.println("Received "+sketches.get(0).name()+", size "+sketches.get(0).keys.length);
755 						System.err.println("params: "+params);
756 						System.err.println("postparsed: "+params.postParsed());
757 						System.err.println("taxwhitelist: "+params.taxFilterWhite);
758 					}
759 					response=compare(sketches, params);
760 //					searcher.compare(sketches, response, params, maxConcurrentSketchCompareThreads); //This is where it gets stuck if comparing takes too long
761 					if(verbose2){System.err.println("Result: '"+response+"'");}
762 				}
763 
764 				bytesOut.addAndGet(response.length());
765 				ServerTools.reply(response, "text/plain", t, verbose2, 200, true);
766 
767 				final long stopTime=System.nanoTime();
768 				final long elapsed=stopTime-instanceStartTime;
769 				if(fileMode){
770 					timeMeasurementsLocal.incrementAndGet();
771 					elapsedTimeLocal.addAndGet(elapsed);
772 					lastTimeLocal.set(elapsed);
773 				}else if(refMode){
774 					timeMeasurementsReference.incrementAndGet();
775 					elapsedTimeReference.addAndGet(elapsed);
776 					lastTimeReference.set(elapsed);
777 				}else{
778 					timeMeasurementsRemote.incrementAndGet();
779 					elapsedTimeRemote.addAndGet(elapsed);
780 					lastTimeRemote.set(elapsed);
781 
782 					queryCounts.incrementAndGet(Tools.min(numSketches, queryCounts.length()-1));
783 					timesByCount.addAndGet(Tools.min(numSketches, queryCounts.length()-1), elapsed);
784 				}
785 			}
786 
parseRparamSketch(HttpExchange t)787 			private String parseRparamSketch(HttpExchange t){
788 				//Parse the query from the URL
789 				String rparam=getRParam(t, false);
790 				if(rparam!=null){bytesIn.addAndGet(rparam.length());}
791 
792 				if(rparam.length()<1 || rparam.equalsIgnoreCase("help") || rparam.equalsIgnoreCase("usage") || rparam.equalsIgnoreCase("help/") || rparam.equalsIgnoreCase("usage/")){
793 					return null;
794 				}
795 
796 				if(rparam.startsWith("sketch/")){rparam=rparam.substring(7);}
797 				else if(rparam.equals("sketch")){rparam="";}
798 				while(rparam.startsWith("/")){rparam=rparam.substring(1);}
799 
800 				//Toggle between local files and sketch transmission
801 
802 				if(rparam.length()<2){
803 					params=SketchObject.defaultParams;
804 				}else{
805 					params=SketchObject.defaultParams.clone();
806 					String[] args=rparam.split("/");
807 					int trimmed=0;
808 					for(int i=0; i<args.length; i++){//parse rparam
809 						String arg=args[i];
810 						if(arg.length()>0){
811 							String[] split=arg.split("=");
812 							String a=split[0].toLowerCase();
813 							String b=split.length>1 ? split[1] : null;
814 
815 							if(a.equals("file")){
816 								fileMode=true;
817 								trimmed+=5;
818 								break;
819 							}else if(a.equals("ref") || a.equals("taxid") || a.equals("tid")){
820 								refMode=true;
821 								trimmed+=4;
822 								break;
823 							}else if(params.parse(arg, a, b)){
824 								trimmed+=arg.length()+1;
825 							}else{
826 								assert(false) : "Bad argument:'"+arg+"'"+"\n"+Arrays.toString(args)+"\n"+rparam;
827 							}
828 						}
829 					}
830 					params.postParse(true, true);
831 //					System.err.println("Trimmed="+trimmed+", rparam="+rparam);
832 					if(trimmed>0){
833 						rparam=rparam.substring(Tools.min(trimmed, rparam.length()));
834 					}
835 //					System.err.println("rparam="+rparam);
836 				}
837 
838 				if(verbose2){
839 					System.err.println(rparam);
840 					System.err.println("rparam.startsWith(\"file/\"):"+rparam.startsWith("file/"));
841 				}
842 
843 				return rparam;
844 			}
845 
846 			private final HttpExchange t;
847 			private DisplayParams params;
848 			private final long instanceStartTime;
849 			private boolean fileMode=false;
850 			private boolean refMode=false;
851 		}
852 
853 	}
854 
findRefSketch(String s)855 	private Sketch findRefSketch(String s){
856 		assert(s!=null);
857 		if(s==null || s.length()<1){return null;}
858 		int tid=-1;
859 		if(Tools.isDigit(s.charAt(0))){tid=Integer.parseInt(s);}
860 		else{
861 			TaxNode tn=getTaxNodeByName(s);
862 			tid=tn==null ? -1 : tn.id;
863 		}
864 		Sketch sk=tid<0 ? null : searcher.findReferenceSketch(tid);
865 		if(sk!=null){sk=(Sketch)sk.clone();}
866 		return sk;
867 	}
868 
869 	/*--------------------------------------------------------------*/
870 
871 	/** Handles taxonomy lookups */
872 	class TaxHandler implements HttpHandler {
873 
874 		public TaxHandler(boolean skipNonCanonical_){
875 			skipNonCanonical=skipNonCanonical_;
876 		}
877 
878 		@Override
879 		public void handle(HttpExchange t) throws IOException {
880 			if(verbose2){System.err.println("Tax handler");}
881 			final long startTime=System.nanoTime();
882 
883 			if(sketchOnly){
884 				ServerTools.reply("\nERROR: This server is tunning in sketch mode and should not be used for taxonomic lookups.\n"
885 						+ "The taxonomy server is at "+Shared.taxServer()+"\n", "text/plain", t, verbose2, 400, true);
886 				return;
887 			}
888 
889 			//Parse the query from the URL
890 			String rparam=getRParam(t, true);
891 
892 
893 			boolean simple=skipNonCanonical;
894 
895 			{//Legacy support for old style of invoking simple
896 				if(rparam.startsWith("simpletax/")){rparam=rparam.substring(7); simple=true;}
897 				else if(rparam.startsWith("stax/")){rparam=rparam.substring(5); simple=true;}
898 				else if(rparam.startsWith("tax/")){rparam=rparam.substring(4);}
899 				else if(rparam.equals("simpletax") || rparam.equals("stax")){rparam=""; simple=true;}
900 				else if(rparam.equals("tax")){rparam="";}
901 			}
902 			while(rparam.startsWith("/")){rparam=rparam.substring(1);}
903 			if(rparam.length()<1 || rparam.equalsIgnoreCase("help") || rparam.equalsIgnoreCase("usage")){
904 				returnUsage(startTime, t);
905 				return;
906 			}
907 
908 			String[] params = rparam.split("/");
909 			if(verbose2){System.err.println(Arrays.toString(params));}
910 
911 			final String response=toResponse(simple, params, t);
912 			final String type=response.startsWith("{") ? "application/json" : "text/plain";
913 
914 			ServerTools.reply(response, type, t, verbose2, 200, true);
915 
916 			final long stopTime=System.nanoTime();
917 			final long elapsed=stopTime-startTime;
918 			if(response.startsWith("Welcome to ")){
919 				timeMeasurementsUsage.incrementAndGet();
920 				elapsedTimeUsage.addAndGet(elapsed);
921 				lastTimeUsage.set(elapsed);
922 			}else{
923 				timeMeasurementsRemote.incrementAndGet();
924 				elapsedTimeRemote.addAndGet(elapsed);
925 				lastTimeRemote.set(elapsed);
926 			}
927 		}
928 
929 		//TODO: Integrate something like this to improve parsing
930 //		String parse(String rparam){
931 //
932 //			if(rparam.length()<2){return rparam;}
933 //
934 //			String[] args=rparam.split("/");
935 //			int trimmed=0;
936 //			for(int i=0; i<args.length; i++){//parse rparam
937 //				String arg=args[i];
938 //				if(arg.length()>0){
939 //					String[] split=arg.split("=");
940 //					String a=split[0].toLowerCase();
941 //					String b=split.length>1 ? split[1] : null;
942 //
943 //					if(a.equals("file")){
944 //						fileMode=true;
945 //						trimmed+=5;
946 //						break;
947 //					}else if(params.parse(arg, a, b)){
948 //						trimmed+=arg.length()+1;
949 //					}else{
950 //						assert(false) : "Bad argument:'"+arg+"'"+"\n"+Arrays.toString(args)+"\n"+rparam;
951 //					}
952 //				}
953 //			}
954 //			params.postParse(true);
955 //			//			System.err.println("Trimmed="+trimmed+", rparam="+rparam);
956 //			if(trimmed>0){
957 //				rparam=rparam.substring(Tools.min(trimmed, rparam.length()));
958 //			}
959 //		}
960 
961 		/** Only print nodes at canonical tax levels */
962 		public final boolean skipNonCanonical;
963 	}
964 
965 	/*--------------------------------------------------------------*/
966 	/*----------------            Helpers           ----------------*/
967 	/*--------------------------------------------------------------*/
968 
969 	static String getBody(HttpExchange t){
970 		if(verbose2){System.err.println("getBody");}
971 		InputStream is=t.getRequestBody();
972 		String s=ServerTools.readStream(is);
973 		return s;
974 	}
975 
976 	/** Parse the query from the URL */
977 	static String getRParam(HttpExchange t, boolean allowPost){
978 		if(verbose2){System.err.println("getRParam");}
979 		String rparam = t.getRequestURI().toString();
980 
981 		//Trim leading slashes
982 		while(rparam.startsWith("/")){
983 			rparam = rparam.substring(1);
984 		}
985 
986 		//Trim trailing slashes
987 		while(rparam.endsWith("/")){
988 			rparam = rparam.substring(0, rparam.length()-1);
989 		}
990 
991 		if(allowPost && ("$POST".equalsIgnoreCase(rparam) || "POST".equalsIgnoreCase(rparam))){
992 			String body=getBody(t);
993 			rparam=body;
994 		}
995 
996 		if(verbose){System.err.println(rparam==null || rparam.trim().length()<1 ? "usage" : rparam+"\t"+System.currentTimeMillis());}
997 		return rparam;
998 	}
999 
1000 	/*--------------------------------------------------------------*/
1001 	/*----------------      Taxonomy Formatting     ----------------*/
1002 	/*--------------------------------------------------------------*/
1003 
1004 	/** All tax queries enter here from the handler */
1005 	String toResponse(boolean simple, String[] params, HttpExchange t){
1006 		if(verbose2){System.err.println("toResponse");}
1007 
1008 		boolean printNumChildren=false;
1009 		boolean printChildren=false;
1010 		boolean printPath=false;
1011 		boolean printSize=false;
1012 		boolean printRange=false;
1013 		boolean silvaHeader=false;
1014 		boolean plaintext=false, semicolon=false, path=false;
1015 		boolean mononomial=false;
1016 		boolean ancestor=false;
1017 		int source=SOURCE_REFSEQ;
1018 
1019 		ArrayList<String> newParams=new ArrayList<String>(params.length);
1020 		for(int i=0; i<params.length-1; i++){
1021 			String s=params[i];
1022 			if(s.length()==0 || s.equals("tax")){
1023 				//Do nothing
1024 			}else if(s.equals("printnumchildren") || s.equals("numchildren")){
1025 				printNumChildren=true;
1026 			}else if(s.equals("printchildren") || s.equals("children")){
1027 				printChildren=true;
1028 			}else if(s.equals("mononomial") || s.equals("cn")
1029 					|| s.equals("fixname") || s.equals("fn") || s.equals("mono") || s.equals("mononomial")){
1030 				mononomial=true;
1031 			}else if(s.equals("printpath") || s.equals("pp")){
1032 				printPath=true;
1033 			}else if(s.equals("printsize") || s.equals("size") || s.equals("ps")){
1034 				printSize=true;
1035 			}else if(s.equals("printrange") || s.equals("range")){
1036 				printRange=true;
1037 			}else if(s.equals("plaintext") || s.equals("pt")){
1038 				plaintext=true; semicolon=false; path=false;
1039 			}else if(s.equals("semicolon") || s.equals("sc")){
1040 				semicolon=true; plaintext=false; path=false;
1041 			}else if(s.equals("path") || s.equals("pa")){
1042 				path=true; semicolon=false; plaintext=false;
1043 			}else if(s.equals("silva")){
1044 				silvaHeader=true;
1045 				source=SOURCE_SILVA;
1046 			}else if(s.equals("refseq")){
1047 				source=SOURCE_REFSEQ;
1048 			}else if(s.equals("ancestor")){
1049 				ancestor=true;
1050 			}else if(s.equals("simple")){
1051 				simple=true;
1052 			}else{
1053 				newParams.add(s);
1054 			}
1055 		}
1056 		newParams.add(params[params.length-1]);
1057 		params=newParams.toArray(new String[newParams.size()]);
1058 
1059 //		System.err.println("mononomial="+mononomial);
1060 
1061 		if(params.length<2){
1062 			if(params.length==1 && "advice".equalsIgnoreCase(params[0])){return TAX_ADVICE;}
1063 			if(logUsage){System.err.println("usage");}
1064 			return USAGE(USAGE);
1065 		}
1066 		if(params.length>3){
1067 			if(logUsage){System.err.println("usage");}
1068 			return USAGE(USAGE);
1069 		}
1070 
1071 		final String query=params[params.length-1];
1072 		final String[] names=query.split(",");
1073 		if(names==null){return USAGE(USAGE);}
1074 
1075 		if(names==null || names.length<2){
1076 			firstChunkSingle.incrementAndGet();
1077 		}else{
1078 			firstChunkMulti.incrementAndGet();
1079 		}
1080 		bulkCount.addAndGet(names.length);
1081 //		System.err.println(params[2]+", "+ancestor);
1082 
1083 		//Raw query type code
1084 		final int type;
1085 		//Type code excluding formatting
1086 		final int type2;
1087 		{
1088 			String typeS=params[0];
1089 			Integer value=typeMap.get(typeS);
1090 			if(value==null){
1091 				if(typeS.equalsIgnoreCase("advice")){
1092 					return TAX_ADVICE;
1093 				}else{
1094 					return "{\"error\": \"Bad type ("+typeS+"); should be gi, taxid, or name.\"}";
1095 				}
1096 			}
1097 			int x=value.intValue();
1098 			if((x&15)==HEADER && silvaHeader){x=SILVAHEADER;}
1099 			type=x;
1100 			type2=x&15;
1101 			if(type2==IMG){source=SOURCE_IMG;}
1102 		}
1103 
1104 		plaintext=(type>=PT_BIT || plaintext);
1105 		semicolon=(type>=SC_BIT || semicolon);
1106 		path=(type>=PA_BIT || path);
1107 		if(semicolon || path){plaintext=false;}
1108 		if(path){semicolon=false;}
1109 
1110 		final boolean internal=incrementQueries(t, false, false, simple, ancestor,
1111 				plaintext, semicolon, path, printChildren, printPath, printSize, type); //Ignores usage information.
1112 
1113 //		if(type2==GI){//123
1114 //			return "{\"error\": \"GI number support is temporarily suspended due to conflicts in NCBI databases.  "
1115 //					+ "It may come back split into nucleotide and protein GI numbers, which currently are not exclusive.\"}";
1116 //		}
1117 
1118 		if(!internal && !allowRemoteFileAccess){
1119 			path=printPath=false;
1120 		}
1121 
1122 		if(verbose2){System.err.println("Type: "+type);}
1123 		if(type2==NAME || type2==HEADER || type2==SILVAHEADER){
1124 			for(int i=0; i<names.length; i++){
1125 				names[i]=PercentEncoding.codeToSymbol(names[i]);
1126 				if(type2==HEADER || type2==SILVAHEADER){
1127 					if(names[i].startsWith("@") || names[i].startsWith(">")){names[i]=names[i].substring(1);}
1128 				}
1129 			}
1130 			if(verbose2){System.err.println("Revised: "+Arrays.toString(names));}
1131 		}
1132 
1133 		if(ancestor){
1134 			if(verbose2){System.err.println("toAncestor: "+Arrays.toString(names));}
1135 			return toAncestor(type, names, plaintext, semicolon, path, query, simple, !simple, printNumChildren, printChildren, printPath, printSize, printRange, mononomial, source);
1136 		}
1137 
1138 		if(semicolon){
1139 			return toSemicolon(type, names, simple, mononomial);
1140 		}else if(plaintext){
1141 			return toText(type, names);
1142 		}else if(path){
1143 			return toPath(type, names, source);
1144 		}
1145 
1146 		JsonObject j=new JsonObject();
1147 		for(String name : names){
1148 			j.add(name, toJson(type, name, simple, !simple, printNumChildren, printChildren,
1149 					printPath, printSize, printRange, mononomial, source));
1150 		}
1151 		return j.toString();
1152 	}
1153 
1154 	/** Look up common ancestor of terms */
1155 	String toAncestor(final int type, final String[] names, boolean plaintext, boolean semicolon, boolean path,
1156 			String query, final boolean skipNonCanonical, boolean originalLevel, boolean printNumChildren,
1157 			boolean printChildren, boolean printPath, boolean printSize, boolean printRange, boolean mononomial,
1158 			int source){
1159 		IntList ilist=toIntList(type, names);
1160 		int id=FindAncestor.findAncestor(tree, ilist);
1161 		TaxNode tn=(id>-1 ? tree.getNode(id) : null);
1162 		if(tn==null){
1163 			return new JsonObject("error","Not found.").toString(query);
1164 		}
1165 		if(semicolon){
1166 			return tree.toSemicolon(tn, skipNonCanonical, mononomial);
1167 		}else if(plaintext){
1168 			return ""+id;
1169 		}else if(path){
1170 			return toPath(tn, source);
1171 		}
1172 
1173 		JsonObject j=new JsonObject();
1174 //		j.add("name", mononomial ? tree.mononomial(tn) : tn.name);
1175 		j.add("name", tn.name);
1176 		if(mononomial || true){
1177 			String mono=tree.mononomial(tn);
1178 			if(tn.name!=mono){j.add("mononomial", mono);}
1179 		}
1180 		j.add("tax_id", tn.id);
1181 		if(printNumChildren){j.add("num_children", tn.numChildren);}
1182 		if(printPath){j.add("path", toPath(tn, source));}
1183 		if(printSize){
1184 			j.add("size", tree.toSize(tn));
1185 			j.add("cumulative_size", tree.toSizeC(tn));
1186 			j.add("seqs", tree.toSeqs(tn));
1187 			j.add("cumulative_seqs", tree.toSeqsC(tn));
1188 			j.add("cumulative_nodes", tree.toNodes(tn));
1189 		}
1190 		j.add("level", tn.levelStringExtended(originalLevel));
1191 		if(tn.levelExtended<1 && printRange){
1192 			j.add("maxDescendent", TaxTree.levelToStringExtended(tn.maxChildLevelExtended));
1193 			j.add("minAncestor", TaxTree.levelToStringExtended(tn.minParentLevelExtended));
1194 		}
1195 //		if(printChildren){j.add(getChildren(id, originalLevel, printRange));}
1196 		while(tn!=null && tn.levelExtended!=TaxTree.LIFE_E && tn.id!=TaxTree.CELLULAR_ORGANISMS_ID){
1197 			if(!skipNonCanonical || tn.isSimple()){
1198 				j.addAndRename(tn.levelStringExtended(originalLevel), toJson(tn, originalLevel, printNumChildren, printChildren, printPath, printSize, printRange, mononomial, source, -1));
1199 			}
1200 			if(tn.pid==tn.id){break;}
1201 			tn=tree.getNode(tn.pid);
1202 		}
1203 		return j.toString();
1204 	}
1205 
1206 	JsonObject getChildren(final int id, boolean originalLevel, boolean printRange, boolean mononomial){
1207 		TaxNode x=tree.getNode(id);
1208 		if(x==null || x.numChildren==0){return null;}
1209 		ArrayList<TaxNode> list=tree.getChildren(x);
1210 		return makeChildrenObject(list, originalLevel, printRange, mononomial);
1211 	}
1212 
1213 	JsonObject makeChildrenObject(ArrayList<TaxNode> list, boolean originalLevel, boolean printRange, boolean mononomial){
1214 		if(list==null || list.isEmpty()){return null;}
1215 		JsonObject j=new JsonObject();
1216 		for(TaxNode tn : list){
1217 			JsonObject child=new JsonObject();
1218 //			child.add("name", mononomial ? tree.mononomial(tn) : tn.name);
1219 			child.add("name", tn.name);
1220 			if(mononomial || true){
1221 				String mono=tree.mononomial(tn);
1222 				if(tn.name!=mono){child.add("mononomial", mono);}
1223 			}
1224 			child.add("tax_id", tn.id);
1225 			child.add("num_children", tn.numChildren);
1226 			child.add("level", tn.levelStringExtended(originalLevel));
1227 			if(tn.levelExtended<1 && printRange){
1228 				child.add("maxDescendent", TaxTree.levelToStringExtended(tn.maxChildLevelExtended));
1229 				child.add("minAncestor", TaxTree.levelToStringExtended(tn.minParentLevelExtended));
1230 			}
1231 			j.add(tn.id+"", child);
1232 		}
1233 		return j;
1234 	}
1235 
1236 	/** Format a reply as plaintext, comma-delimited, TaxID only */
1237 	String toText(final int type, final String[] names){
1238 
1239 		StringBuilder sb=new StringBuilder();
1240 		String comma="";
1241 
1242 		int type2=type&15;
1243 		if(type2==GI){
1244 			for(String name : names){
1245 				sb.append(comma);
1246 				TaxNode tn=getTaxNodeGi(Integer.parseInt(name));
1247 				if(tn==null){sb.append("-1");}
1248 				else{sb.append(tn.id);}
1249 				comma=",";
1250 			}
1251 		}else if(type2==NAME){
1252 			for(String name : names){
1253 				sb.append(comma);
1254 				TaxNode tn=getTaxNodeByName(name);
1255 				if(tn==null){sb.append("-1");}
1256 				else{sb.append(tn.id);}
1257 				comma=",";
1258 			}
1259 		}else if(type2==TAXID){
1260 			for(String name : names){
1261 				sb.append(comma);
1262 				TaxNode tn=getTaxNodeTaxid(Integer.parseInt(name));
1263 				if(tn==null){sb.append("-1");}
1264 				else{sb.append(tn.id);}
1265 				comma=",";
1266 			}
1267 		}else if(type2==ACCESSION){
1268 			for(String name : names){
1269 				sb.append(comma);
1270 				int ncbi=accessionToTaxid(name);
1271 				sb.append(ncbi);
1272 				comma=",";
1273 			}
1274 		}else if(type2==HEADER || type2==SILVAHEADER){
1275 			for(String name : names){
1276 				sb.append(comma);
1277 				TaxNode tn=getTaxNodeHeader(name, type2==SILVAHEADER);
1278 				if(tn==null){sb.append("-1");}
1279 				else{sb.append(tn.id);}
1280 				comma=",";
1281 			}
1282 		}else if(type2==IMG){
1283 			for(String name : names){
1284 				sb.append(comma);
1285 				int ncbi=TaxTree.imgToTaxid(Long.parseLong(name));
1286 				sb.append(ncbi);
1287 				comma=",";
1288 			}
1289 		}else{
1290 			return "Bad type; should be pt_gi or pt_name; e.g. /pt_gi/1234";
1291 		}
1292 
1293 		return sb.toString();
1294 	}
1295 
1296 	private TaxNode toNode(final int type, final String name){
1297 		int type2=type&15;
1298 		final TaxNode tn;
1299 		if(type2==GI){
1300 			tn=getTaxNodeGi(Integer.parseInt(name));
1301 		}else if(type2==NAME){
1302 			tn=getTaxNodeByName(name);
1303 		}else if(type2==TAXID){
1304 			tn=getTaxNodeTaxid(Integer.parseInt(name));
1305 		}else if(type2==ACCESSION){
1306 			int ncbi=accessionToTaxid(name);
1307 			tn=(ncbi<0 ? null : tree.getNode(ncbi));
1308 		}else if(type2==HEADER || type2==SILVAHEADER){
1309 			tn=getTaxNodeHeader(name, type2==SILVAHEADER);
1310 		}else if(type2==IMG){
1311 			int ncbi=TaxTree.imgToTaxid(Long.parseLong(name));
1312 			tn=(ncbi<0 ? null : tree.getNode(ncbi));
1313 		}else{
1314 			tn=null;
1315 		}
1316 		return tn;
1317 	}
1318 
1319 	/** Format a reply as paths, comma-delimited*/
1320 	String toPath(final int type, final String[] names, final int source){
1321 
1322 		StringBuilder sb=new StringBuilder();
1323 		String comma="";
1324 
1325 		int type2=type&15;
1326 
1327 		for(String name : names){
1328 			sb.append(comma);
1329 			if(type2==IMG){
1330 				sb.append(toPathIMG(Long.parseLong(name)));
1331 			}else{
1332 				TaxNode tn=toNode(type2, name);
1333 				sb.append(toPath(tn, source));
1334 			}
1335 			comma=",";
1336 		}
1337 
1338 		return sb.toString();
1339 	}
1340 
1341 	/** Format a reply as plaintext, semicolon-delimited, full lineage */
1342 	String toSemicolon(final int type, final String[] names, boolean skipNonCanonical, boolean mononomial){
1343 
1344 		StringBuilder sb=new StringBuilder();
1345 		String comma="";
1346 
1347 		int type2=type&15;
1348 		if(type2==GI){
1349 			for(String name : names){
1350 				sb.append(comma);
1351 				TaxNode tn=getTaxNodeGi(Integer.parseInt(name));
1352 				if(tn==null){sb.append("Not found");}
1353 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1354 				comma=",";
1355 			}
1356 		}else if(type2==NAME){
1357 			for(String name : names){
1358 				sb.append(comma);
1359 				TaxNode tn=getTaxNodeByName(name);
1360 				if(tn==null){sb.append("Not found");}
1361 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1362 				comma=",";
1363 			}
1364 		}else if(type2==TAXID){
1365 			for(String name : names){
1366 				sb.append(comma);
1367 				TaxNode tn=getTaxNodeTaxid(Integer.parseInt(name));
1368 //				if(verbose2){outstream.println("name="+name+", tn="+tn);}
1369 				if(tn==null){sb.append("Not found");}
1370 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1371 				comma=",";
1372 			}
1373 		}else if(type2==ACCESSION){
1374 			for(String name : names){
1375 				sb.append(comma);
1376 				final int tid=accessionToTaxid(name);
1377 				TaxNode tn=tree.getNode(tid, true);
1378 				if(tn==null){sb.append("Not found");}
1379 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1380 				comma=",";
1381 			}
1382 		}else if(type2==HEADER || type2==SILVAHEADER){
1383 			for(String name : names){
1384 				sb.append(comma);
1385 				TaxNode tn=getTaxNodeHeader(name, type2==SILVAHEADER);
1386 				if(tn==null){sb.append("Not found");}
1387 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1388 				comma=",";
1389 			}
1390 		}else if(type2==IMG){
1391 			for(String name : names){
1392 				sb.append(comma);
1393 				final int tid=TaxTree.imgToTaxid(Long.parseLong(name));
1394 				TaxNode tn=tree.getNode(tid, true);
1395 				if(tn==null){sb.append("Not found");}
1396 				else{sb.append(tree.toSemicolon(tn, skipNonCanonical, mononomial));}
1397 				comma=",";
1398 			}
1399 		}else{
1400 			return "Bad type; should be sc_gi or sc_name; e.g. /sc_gi/1234";
1401 		}
1402 
1403 //		if(verbose2){outstream.println("In toSemicolon; type="+type+", type2="+type2+", made "+sb);}
1404 
1405 		return sb.toString();
1406 	}
1407 
1408 	/** Create a JsonObject from a String, including full lineage */
1409 	JsonObject toJson(final int type, final String name, boolean skipNonCanonical, boolean originalLevel,
1410 			boolean printNumChildren, boolean printChildren, boolean printPath, boolean printSize,
1411 			boolean printRange, boolean mononomial, int source){
1412 		final TaxNode tn0;
1413 		TaxNode tn;
1414 
1415 		long img=-1;
1416 		if(type==GI){
1417 			tn0=getTaxNodeGi(Integer.parseInt(name));
1418 		}else if(type==NAME){
1419 			tn0=getTaxNodeByName(name);
1420 		}else if(type==TAXID){
1421 			tn0=getTaxNodeTaxid(Integer.parseInt(name));
1422 		}else if(type==ACCESSION){
1423 			int ncbi=accessionToTaxid(name);
1424 			tn0=(ncbi>=0 ? tree.getNode(ncbi) : null);
1425 		}else if(type==HEADER || type==SILVAHEADER){
1426 			tn0=getTaxNodeHeader(name, type==SILVAHEADER);
1427 		}else if(type==IMG){
1428 			img=Long.parseLong(name);
1429 			final int tid=TaxTree.imgToTaxid(img);
1430 			tn0=tree.getNode(tid, true);
1431 		}else{
1432 			JsonObject j=new JsonObject("error","Bad type; should be gi, taxid, or name; e.g. /name/homo_sapiens");
1433 			j.add("name", name);
1434 			j.add("type", type);
1435 			return j;
1436 		}
1437 		tn=tn0;
1438 		if(verbose2){System.err.println("Got node: "+tn);}
1439 
1440 		if(tn!=null){
1441 			JsonObject j=new JsonObject();
1442 //			j.add("name", mononomial ? tree.mononomial(tn) : tn.name);
1443 			j.add("name", tn.name);
1444 			if(mononomial || true){
1445 				String mono=tree.mononomial(tn);
1446 				if(tn.name!=mono){j.add("mononomial", mono);}
1447 			}
1448 			j.add("tax_id", tn.id);
1449 			if(printNumChildren){j.add("num_children", tn.numChildren);}
1450 			if(printPath){j.add("path", type==IMG ? toPathIMG(img) : toPath(tn, source));}
1451 			if(printSize){
1452 				j.add("size", tree.toSize(tn));
1453 				j.add("cumulative_size", tree.toSizeC(tn));
1454 				j.add("seqs", tree.toSeqs(tn));
1455 				j.add("cumulative_seqs", tree.toSeqsC(tn));
1456 				j.add("cumulative_nodes", tree.toNodes(tn));
1457 			}
1458 			j.add("level", tn.levelStringExtended(originalLevel));
1459 			if(tn.levelExtended<1 && printRange){
1460 				j.add("maxDescendent", TaxTree.levelToStringExtended(tn.maxChildLevelExtended));
1461 				j.add("minAncestor", TaxTree.levelToStringExtended(tn.minParentLevelExtended));
1462 			}
1463 			if(printChildren && (tn.id==tn.pid || tn.id==TaxTree.CELLULAR_ORGANISMS_ID)){j.add("children", getChildren(tn.id, originalLevel, printRange, mononomial));}
1464 			while(tn!=null && tn.levelExtended!=TaxTree.LIFE_E && tn.id!=TaxTree.CELLULAR_ORGANISMS_ID){
1465 //				System.err.println(tn+", "+(!skipNonCanonical)+", "+tn.isSimple());
1466 				if(!skipNonCanonical || tn.isSimple()){
1467 					j.addAndRename(tn.levelStringExtended(originalLevel), toJson(tn, originalLevel, printNumChildren, printChildren, printPath && tn==tn0, printSize, printRange, mononomial, source, img));
1468 //					System.err.println(j);
1469 				}
1470 				if(tn.pid==tn.id){break;}
1471 				tn=tree.getNode(tn.pid);
1472 			}
1473 			return j;
1474 		}
1475 		{
1476 			JsonObject j=new JsonObject("error","Not found.");
1477 			j.add("name", name);
1478 			j.add("type", type);
1479 			return j;
1480 		}
1481 	}
1482 
1483 	/** Create a JsonObject from a TaxNode, at that level only */
1484 	JsonObject toJson(TaxNode tn, boolean originalLevel, boolean printNumChildren,
1485 			boolean printChildren, boolean printPath, boolean printSize, boolean printRange,
1486 			boolean mononomial, int source, long img){
1487 		JsonObject j=new JsonObject();
1488 //		j.add("name", mononomial ? tree.mononomial(tn) : tn.name);
1489 		j.add("name", tn.name);
1490 		if(mononomial || true){
1491 			String mono=tree.mononomial(tn);
1492 			if(tn.name!=mono){j.add("mononomial", mono);}
1493 		}
1494 		j.add("tax_id", tn.id);
1495 		if(printNumChildren){j.add("num_children", tn.numChildren);}
1496 		if(printPath){j.add("path", source==SOURCE_IMG ? toPathIMG(img) : toPath(tn, source));}
1497 		if(printSize){
1498 			j.add("size", tree.toSize(tn));
1499 			j.add("cumulative_size", tree.toSizeC(tn));
1500 			j.add("seqs", tree.toSeqs(tn));
1501 			j.add("cumulative_seqs", tree.toSeqsC(tn));
1502 			j.add("cumulative_nodes", tree.toNodes(tn));
1503 		}
1504 		if(tn.levelExtended<1 && printRange){
1505 			j.add("maxDescendent", TaxTree.levelToStringExtended(tn.maxChildLevelExtended));
1506 			j.add("minAncestor", TaxTree.levelToStringExtended(tn.minParentLevelExtended));
1507 		}
1508 		if(printChildren){
1509 			JsonObject children=getChildren(tn.id, originalLevel, printRange, mononomial);
1510 			if(children!=null){j.add("children", children);}
1511 		}
1512 		return j;
1513 	}
1514 
1515 	String toPath(TaxNode tn, int source){
1516 		if(tn==null){return "null";}
1517 		String path;
1518 		if(source==SOURCE_REFSEQ){
1519 			path=tree.toDir(tn, basePath)+"refseq_"+tn.id+".fa.gz";
1520 		}else if(source==SOURCE_SILVA){
1521 			path=tree.toDir(tn, basePath)+"silva_"+tn.id+".fa.gz";
1522 		}else if(source==SOURCE_IMG){
1523 			assert(false);
1524 			path="null";
1525 		}else{
1526 			assert(false);
1527 			path="null";
1528 		}
1529 		if(!path.equals("null") && !new File(path).exists()){path="null";}
1530 		return path;
1531 	}
1532 
1533 	String toPathIMG(long imgID){
1534 		String path="/global/dna/projectdirs/microbial/img_web_data/taxon.fna/"+imgID+".fna";
1535 		if(!new File(path).exists()){path="null";}
1536 		return path;
1537 	}
1538 
1539 	/*--------------------------------------------------------------*/
1540 	/*----------------        Taxonomy Lookup       ----------------*/
1541 	/*--------------------------------------------------------------*/
1542 
1543 	/** Convert a list of terms to a list of TaxIDs */
1544 	IntList toIntList(final int type, final String[] names){
1545 		IntList list=new IntList(names.length);
1546 		int type2=type&15;
1547 		if(type2==GI){
1548 			for(String name : names){
1549 				TaxNode tn=getTaxNodeGi(Integer.parseInt(name));
1550 				if(tn!=null){list.add(tn.id);}
1551 				else{notFound.incrementAndGet();}
1552 			}
1553 		}else if(type2==NAME){
1554 			for(String name : names){
1555 				TaxNode tn=getTaxNodeByName(name);
1556 				if(tn!=null){list.add(tn.id);}
1557 				else{notFound.incrementAndGet();}
1558 			}
1559 		}else if(type2==TAXID){
1560 			for(String name : names){
1561 				TaxNode tn=getTaxNodeTaxid(Integer.parseInt(name));
1562 				if(tn!=null){list.add(tn.id);}
1563 				else{notFound.incrementAndGet();}
1564 			}
1565 		}else if(type2==ACCESSION){
1566 			for(String name : names){
1567 				int ncbi=accessionToTaxid(name);
1568 				if(ncbi>=0){list.add(ncbi);}
1569 				else{notFound.incrementAndGet();}
1570 			}
1571 		}else if(type2==IMG){
1572 			for(String name : names){
1573 				final int tid=TaxTree.imgToTaxid(Long.parseLong(name));
1574 				if(tid>=0){list.add(tid);}
1575 				else{notFound.incrementAndGet();}
1576 			}
1577 		}else{
1578 			throw new RuntimeException("{\"error\": \"Bad type\"}");
1579 		}
1580 		return list;
1581 	}
1582 
1583 	public static final String stripAccession(String s){
1584 		if(s==null){return null;}
1585 		s=s.toUpperCase();
1586 		for(int i=0; i<s.length(); i++){
1587 			char c=s.charAt(i);
1588 			if(c=='.' || c==':'){return s.substring(0, i);}
1589 		}
1590 		return s;
1591 	}
1592 
1593 	private int accessionToTaxid(String accession){
1594 		if(accession==null){return -1;}
1595 		int tid=AccessionToTaxid.get(accession);
1596 		if(tid<0 && distributed && serverNum==0){
1597 			accession=stripAccession(accession);
1598 			int slaveNum=accession.hashCode()%serverCount;
1599 			if(slaveNum!=serverNum){
1600 				String path=slaveAddress.get(slaveNum);
1601 				tid=TaxClient.accessionToTaxidSpecificServer(path, accession);
1602 			}
1603 		}
1604 		return tid;
1605 	}
1606 
1607 	/** Look up a TaxNode by parsing the organism name */
1608 	TaxNode getTaxNodeByName(String name){
1609 		if(verbose2){System.err.println("Fetching node for "+name);}
1610 		List<TaxNode> list=tree.getNodesByNameExtended(name);
1611 		if(verbose2){System.err.println("Fetched "+list);}
1612 		if(list==null){
1613 			if(verbose2){System.err.println("Fetched in common map "+name);}
1614 			String name2=commonMap.get(name);
1615 			if(verbose2){System.err.println("Fetched "+name2);}
1616 			if(name2!=null){list=tree.getNodesByName(name2);}
1617 		}
1618 		if(list==null){notFound.incrementAndGet();}
1619 		return list==null ? null : list.get(0);
1620 	}
1621 
1622 	/** Look up a TaxNode from the gi number */
1623 	TaxNode getTaxNodeGi(int gi){
1624 		int ncbi=-1;
1625 		try {
1626 			ncbi=GiToTaxid.getID(gi);
1627 		} catch (Throwable e) {
1628 			if(verbose){e.printStackTrace();}
1629 		}
1630 		if(ncbi<0){notFound.incrementAndGet();}
1631 		return ncbi<0 ? null : getTaxNodeTaxid(ncbi);
1632 	}
1633 
1634 	/** Look up a TaxNode by parsing the full header */
1635 	TaxNode getTaxNodeHeader(String header, boolean silvaMode){
1636 		TaxNode tn=silvaMode ? tree.getNodeSilva(header, true) : tree.parseNodeFromHeader(header, true);
1637 		if(tn==null){notFound.incrementAndGet();}
1638 		return tn;
1639 	}
1640 
1641 	/** Look up a TaxNode from the ncbi TaxID */
1642 	TaxNode getTaxNodeTaxid(int ncbi){
1643 		TaxNode tn=null;
1644 		try {
1645 			tn=tree.getNode(ncbi);
1646 		} catch (Throwable e) {
1647 			if(verbose){e.printStackTrace();}
1648 		}
1649 		if(tn==null){notFound.incrementAndGet();}
1650 		return tn;
1651 	}
1652 
1653 	/*--------------------------------------------------------------*/
1654 	/*----------------     Data Initialization      ----------------*/
1655 	/*--------------------------------------------------------------*/
1656 
1657 	private static HashMap<String, Integer> makeTypeMap() {
1658 		HashMap<String, Integer> map=new HashMap<String, Integer>(63);
1659 		map.put("gi", GI);
1660 //		map.put("ngi", NGI);
1661 //		map.put("pgi", PGI);
1662 		map.put("name", NAME);
1663 		map.put("tax_id", TAXID);
1664 		map.put("ncbi", TAXID);
1665 		map.put("taxid", TAXID);
1666 		map.put("id", TAXID);
1667 		map.put("tid", TAXID);
1668 		map.put("header", HEADER);
1669 		map.put("accession", ACCESSION);
1670 		map.put("img", IMG);
1671 		map.put("silvaheader", SILVAHEADER);
1672 
1673 		map.put("pt_gi", PT_GI);
1674 //		map.put("pt_ngi", PT_NGI);
1675 //		map.put("pt_pgi", PT_PGI);
1676 		map.put("pt_name", PT_NAME);
1677 		map.put("pt_tax_id", PT_TAXID);
1678 		map.put("pt_id", PT_TAXID);
1679 		map.put("pt_tid", PT_TAXID);
1680 		map.put("pt_ncbi", PT_TAXID);
1681 		map.put("pt_taxid", PT_TAXID);
1682 		map.put("pt_header", PT_HEADER);
1683 		map.put("pt_header", PT_HEADER);
1684 		map.put("pt_accession", PT_ACCESSION);
1685 		map.put("pt_img", PT_IMG);
1686 		map.put("pt_silvaheader", PT_SILVAHEADER);
1687 
1688 		map.put("sc_gi", SC_GI);
1689 //		map.put("sc_ngi", SC_NGI);
1690 //		map.put("sc_pgi", SC_PGI);
1691 		map.put("sc_name", SC_NAME);
1692 		map.put("sc_tax_id", SC_TAXID);
1693 		map.put("sc_id", SC_TAXID);
1694 		map.put("sc_tid", SC_TAXID);
1695 		map.put("sc_ncbi", SC_TAXID);
1696 		map.put("sc_taxid", SC_TAXID);
1697 		map.put("sc_header", SC_HEADER);
1698 		map.put("sc_header", SC_HEADER);
1699 		map.put("sc_accession", SC_ACCESSION);
1700 		map.put("sc_silvaheader", SC_SILVAHEADER);
1701 
1702 		return map;
1703 	}
1704 
1705 	public static HashMap<String, String> makeCommonMap(){
1706 		HashMap<String, String> map=new HashMap<String, String>();
1707 		map.put("human", "homo sapiens");
1708 		map.put("cat", "felis catus");
1709 		map.put("dog", "canis lupus familiaris");
1710 		map.put("mouse", "mus musculus");
1711 		map.put("cow", "bos taurus");
1712 		map.put("bull", "bos taurus");
1713 		map.put("horse", "Equus ferus");
1714 		map.put("pig", "Sus scrofa domesticus");
1715 		map.put("sheep", "Ovis aries");
1716 		map.put("goat", "Capra aegagrus");
1717 		map.put("turkey", "Meleagris gallopavo");
1718 		map.put("fox", "Vulpes vulpes");
1719 		map.put("chicken", "Gallus gallus domesticus");
1720 		map.put("wolf", "canis lupus");
1721 		map.put("fruitfly", "drosophila melanogaster");
1722 		map.put("zebrafish", "Danio rerio");
1723 		map.put("catfish", "Ictalurus punctatus");
1724 		map.put("trout", "Oncorhynchus mykiss");
1725 		map.put("salmon", "Salmo salar");
1726 		map.put("tilapia", "Oreochromis niloticus");
1727 		map.put("e coli", "Escherichia coli");
1728 		map.put("e.coli", "Escherichia coli");
1729 
1730 		map.put("lion", "Panthera leo");
1731 		map.put("tiger", "Panthera tigris");
1732 		map.put("bear", "Ursus arctos");
1733 		map.put("deer", "Odocoileus virginianus");
1734 		map.put("coyote", "Canis latrans");
1735 
1736 		map.put("corn", "Zea mays subsp. mays");
1737 		map.put("maize", "Zea mays subsp. mays");
1738 		map.put("oat", "Avena sativa");
1739 		map.put("wheat", "Triticum aestivum");
1740 		map.put("rice", "Oryza sativa");
1741 		map.put("potato", "Solanum tuberosum");
1742 		map.put("barley", "Hordeum vulgare");
1743 		map.put("poplar", "Populus alba");
1744 		map.put("lettuce", "Lactuca sativa");
1745 		map.put("beet", "Beta vulgaris");
1746 		map.put("strawberry", "Fragaria x ananassa");
1747 		map.put("orange", "Citrus sinensis");
1748 		map.put("lemon", "Citrus limon");
1749 		map.put("soy", "Glycine max");
1750 		map.put("soybean", "Glycine max");
1751 		map.put("grape", "Vitis vinifera");
1752 		map.put("olive", "Olea europaea");
1753 		map.put("cotton", "Gossypium hirsutum");
1754 		map.put("apple", "Malus pumila");
1755 		map.put("bannana", "Musa acuminata");
1756 		map.put("tomato", "Solanum lycopersicum");
1757 		map.put("sugarcane", "Saccharum officinarum");
1758 		map.put("bean", "Phaseolus vulgaris");
1759 		map.put("onion", "Allium cepa");
1760 		map.put("garlic", "Allium sativum");
1761 
1762 		map.put("pichu", "mus musculus");
1763 		map.put("pikachu", "mus musculus");
1764 		map.put("vulpix", "Vulpes vulpes");
1765 		map.put("ninetails", "Vulpes vulpes");
1766 		map.put("mareep", "Ovis aries");
1767 
1768 		return map;
1769 	}
1770 
1771 	//Customize usage message to include domain
1772 	private String makeUsagePrefix(){
1773 		if(!sketchOnly){
1774 			return "Welcome to the JGI taxonomy server!\n"
1775 					+ "This service provides taxonomy information from NCBI taxID numbers, gi numbers, organism names, and accessions.\n"
1776 					+ "The output is formatted as a Json object.\n\n"
1777 					+ "Usage:\n\n"
1778 					+ "All addresses below are assumed to be prefixed by "+domain+", e.g. /name/homo_sapiens implies a full URL of:\n"
1779 					+ domain+"/name/homo_sapiens\n"
1780 					+ "\n"
1781 					+ "/name/homo_sapiens will give taxonomy information for an organism name.\n"
1782 					+ "Names are case-insensitive and underscores are equivalent to spaces.\n"
1783 					+ "/id/9606 will give taxonomy information for an NCBI taxID.\n"
1784 					+ "/gi/1234 will give taxonomy information from an NCBI gi number.\n"
1785 
1786 //					+ "\n****NOTICE**** gi number support is temporarily suspended due to conflicts in NCBI data.\n"
1787 //					+ "Support may be restored, altered, or discontinued pending a response from NCBI.\n"
1788 //					+ "Currently, it is not possible to ensure correct results when looking up a GI number, because some map to multiple organisms.\n\n"
1789 
1790 					+ "/accession/NZ_AAAA01000057.1 will give taxonomy information from an accession.\n"
1791 					+ "/header/ will accept an NCBI sequence header such as gi|7|emb|X51700.1| Bos taurus\n"
1792 					+ "/silvaheader/ will accept a Silva sequence header such as KC415233.1.1497 Bacteria;Spirochaetae;Spirochaetes\n"
1793 					+ "/img/ will accept an IMG id such as 2724679250\n"
1794 					+ "Vertical bars (|) may cause problems on the command line and can be replaced by tilde (~).\n"
1795 					+ "\nComma-delimited lists are accepted for bulk queries, such as tax/gi/1234,7000,42\n"
1796 					+ "For plaintext (non-Json) results, add the term /pt/ or /sc/.\n"
1797 					+ "pt will give just the taxID, while sc will give the whole lineage, semicolon-delimited. For example:\n"
1798 					+ "/pt/name/homo_sapiens\n"
1799 					+ "/sc/gi/1234\n\n"
1800 					+ "Additional supported display options are children, numchildren, range, simple, path, size, and ancestor.\n"
1801 					+ "The order is not important but they need to come before the query term.  For example:\n"
1802 					+ "/children/numchildren/range/gi/1234\n"
1803 					+ "\nTo find the common ancestor of multiple organisms, add /ancestor/. For example:\n"
1804 					+ "/id/ancestor/1234,5678,42\n"
1805 					+ "/name/ancestor/homo_sapiens,canis_lupus,bos_taurus\n"
1806 					+ "\nFor a simplified taxonomic tree, add simple.\n"
1807 					+ "This will ignore unranked or uncommon levels like tribe and parvorder, and only display the following levels:\n"
1808 					+ "SUBSPECIES, SPECIES, GENUS, FAMILY, ORDER, CLASS, PHYLUM, KINGDOM, SUPERKINGDOM, DOMAIN\n"
1809 					+ "For example:\n"
1810 					+ "/simple/id/1234\n"
1811 					+ "\nTo print taxonomy from the command line in Linux, use curl:\n"
1812 					+ "curl https://taxonomy.jgi.doe.gov/id/9606\n"
1813 					+ "\nQueries longer than around 8kB can be sent via POST: curl https://taxonomy..doe.gov/POST"
1814 					+ "\n...where the data sent is, for example: name/e.coli,h.sapiens,c.lupus\n"
1815 					+ "\nLast restarted "+startTime+"\n"
1816 					+ "Running BBMap version "+Shared.BBMAP_VERSION_STRING+"\n";
1817 		}else{
1818 			StringBuilder sb=new StringBuilder();
1819 			sb.append("Welcome to the JGI"+(SketchObject.defaultParams.dbName==null ? "" : " "+SketchObject.defaultParams.dbName)+" sketch server!\n");
1820 //			if(dbName!=null){
1821 //				sb.append("This server has the "+dbName+ " database loaded.\n");
1822 //			}
1823 			sb.append("\nUsage:\n\n");
1824 			sb.append("sendsketch.sh in=file.fasta"+(SketchObject.defaultParams.dbName==null ? "" : " "+SketchObject.defaultParams.dbName.toLowerCase())+"\n\n");
1825 			sb.append("SendSketch creates a sketch from a local sequence file, and sends the sketch to this server.\n");
1826 			sb.append("The server receives the sketch, compares it to all sketches in memory, and returns the results.\n");
1827 			sb.append("For files on the same system as the server, the 'local' flag may be used to offload sketch creation to the server.\n");
1828 			sb.append("For more details and parameters please run sendsketch.sh with no arguments.\n");
1829 			sb.append("\n");
1830 			if(SketchObject.useWhitelist()){
1831 				sb.append("This server is running in whitelist mode; for best results, use local queries.\n");
1832 				sb.append("Remote queries should specify a larger-than-normal sketch size.\n\n");
1833 			}else if(SketchObject.blacklist()!=null){
1834 				sb.append("This server is running in blacklist mode, using "+new File(SketchObject.blacklist()).getName()+".\n\n");
1835 			}
1836 			sb.append("Last restarted "+startTime+"\n");
1837 			sb.append("Running BBMap version "+Shared.BBMAP_VERSION_STRING+"\n");
1838 			sb.append("Settings:\tk="+SketchObject.k+(SketchObject.k2>0 ? ","+SketchObject.k2 : ""));
1839 			if(SketchObject.amino){sb.append(" amino");}
1840 			if(SketchObject.makeIndex){sb.append(" index");}
1841 			if(SketchObject.useWhitelist()){sb.append(" whitelist");}
1842 			if(SketchObject.blacklist()!=null){sb.append(" blacklist="+new File(SketchObject.blacklist()).getName());}
1843 			sb.append('\n');
1844 			return sb.toString();
1845 		}
1846 	}
1847 
1848 	private String makeUsageHtml(){
1849 		String html=rawHtml;
1850 		html=html.replace("STATISTICSSTRING", makeStats());
1851 //		html=html.replace("TIMESTAMPSTRING", startTime);
1852 //		html=html.replace("VERSIONSTRING", "Running BBMap version "+Shared.BBMAP_VERSION_STRING);
1853 		return html;
1854 	}
1855 
1856 	private String loadRawHtml(){
1857 		String path=Data.findPath("?tax_server.html");
1858 		String html=ReadWrite.readString(path);
1859 		return html;
1860 	}
1861 
1862 	private String makeStats(){
1863 		ByteBuilder sb=new ByteBuilder();
1864 
1865 		if(!sketchOnly){
1866 			sb.append("JGI taxonomy server stats:\n"
1867 					+ "\nLast restarted "+startTime+"\n"
1868 					+ "Running BBMap version "+Shared.BBMAP_VERSION_STRING+"\n");
1869 		}else{
1870 			sb.append("JGI"+(SketchObject.defaultParams.dbName==null ? "" : " "+SketchObject.defaultParams.dbName)+" sketch server stats:\n\n");
1871 
1872 			if(domain!=null) {sb.append("Domain: "+domain+"\n");}
1873 			if(SketchObject.useWhitelist()){
1874 				sb.append("This server is running in whitelist mode; for best results, use local queries.\n");
1875 				sb.append("Remote queries should specify a larger-than-normal sketch size.\n\n");
1876 			}else if(SketchObject.blacklist()!=null){
1877 				sb.append("This server is running in blacklist mode, using "+new File(SketchObject.blacklist()).getName()+".\n\n");
1878 			}
1879 			sb.append("Last restarted "+startTime+"\n");
1880 			sb.append("Running BBMap version "+Shared.BBMAP_VERSION_STRING+"\n");
1881 			sb.append("Settings: k="+SketchObject.k+(SketchObject.k2>0 ? ","+SketchObject.k2 : ""));
1882 			if(SketchObject.amino){sb.append(" amino");}
1883 			if(SketchObject.makeIndex){sb.append(" index");}
1884 			if(SketchObject.useWhitelist()){sb.append(" whitelist");}
1885 			if(SketchObject.blacklist()!=null){sb.append(" blacklist="+new File(SketchObject.blacklist()).getName());}
1886 		}
1887 		sb.nl().nl();
1888 		sb.append(basicStats());
1889 		if(sketchOnly){sb.append(makeExtendedStats());}
1890 
1891 		return sb.toString();
1892 	}
1893 
1894 	public String makeExtendedStats(){
1895 		ByteBuilder sb=new ByteBuilder();
1896 		sb.append('\n');
1897 
1898 		{
1899 			sb.append("\nVersion\tCount\n");
1900 			ArrayList<String> list=new ArrayList<String>();
1901 			for(Entry<String, StringNum> e : versionMap.entrySet()){
1902 				list.add(e.getValue().toString());
1903 			}
1904 			Collections.sort(list);
1905 			for(String s : list){
1906 				sb.append(s).append('\n');
1907 			}
1908 		}
1909 
1910 		{
1911 			sb.append("\nSketchs\tCount\tAvgTime\n");
1912 			for(int i=0; i<timesByCount.length(); i++){
1913 				double a=timesByCount.get(i)/1000000.0;
1914 				long b=queryCounts.get(i);
1915 				if(b>0){
1916 					sb.append(i).append('\t').append(b).append('\t').append(a/b, 3).append('\n');
1917 				}
1918 			}
1919 			sb.append('\n');
1920 		}
1921 		return sb.toString();
1922 	}
1923 
1924 	public String USAGE(String prefix){
1925 		if(!countQueries){return prefix;}
1926 		String basicStats=basicStats();
1927 		return (prefix==null ? basicStats : prefix+"\n"+basicStats);
1928 	}
1929 
1930 	public String basicStats(){
1931 		if(!countQueries){return "";}
1932 		StringBuilder sb=new StringBuilder(500);
1933 
1934 		final long uq=usageQueries.getAndIncrement();
1935 		final long mq=malformedQueries.get();
1936 		final long pt=plaintextQueries.get(), sc=semicolonQueries.get(), pa=pathQueries.get(), pp=printPathQueries.get(), ps=printSizeQueries.get();
1937 		final long iq=internalQueries.get();
1938 		final long lq=localQueries.get();
1939 		final long rfq=refQueries.get();
1940 		final long q=queries.get();
1941 		final long nf=notFound.get();
1942 		final double avgTimeDL=.000001*(elapsedTimeLocal.get()/(Tools.max(1.0, timeMeasurementsLocal.get())));//in milliseconds
1943 		final double lastTimeDL=.000001*lastTimeLocal.get();
1944 		final double avgTimeDR=.000001*(elapsedTimeRemote.get()/(Tools.max(1.0, timeMeasurementsRemote.get())));//in milliseconds
1945 		final double lastTimeDR=.000001*lastTimeRemote.get();
1946 		final double avgTimeDRF=.000001*(elapsedTimeReference.get()/(Tools.max(1.0, timeMeasurementsReference.get())));//in milliseconds
1947 		final double lastTimeDRF=.000001*lastTimeReference.get();
1948 		final double avgTimeDU=.000001*(elapsedTimeUsage.get()/(Tools.max(1.0, timeMeasurementsUsage.get())));//in milliseconds
1949 		final double lastTimeDU=.000001*lastTimeUsage.get();
1950 		final long exq=q-iq;
1951 		final long rmq=q-lq;
1952 
1953 		sb.append('\n').append("Queries:   ").append(q);
1954 		sb.append('\n').append("Usage:     ").append(uq);
1955 		if(sketchOnly){
1956 			sb.append('\n').append("Invalid:   ").append(mq);
1957 			sb.append('\n').append("Avg time:  ").append(String.format(Locale.ROOT, "%.3f ms (local queries)", avgTimeDL));
1958 			sb.append('\n').append("Last time: ").append(String.format(Locale.ROOT, "%.3f ms (local queries)", lastTimeDL));
1959 			sb.append('\n').append("Avg time:  ").append(String.format(Locale.ROOT, "%.3f ms (remote queries)", avgTimeDR));
1960 			sb.append('\n').append("Last time: ").append(String.format(Locale.ROOT, "%.3f ms (remote queries)", lastTimeDR));
1961 			sb.append('\n').append("Avg time:  ").append(String.format(Locale.ROOT, "%.3f ms (ref queries)", avgTimeDRF));
1962 			sb.append('\n').append("Last time: ").append(String.format(Locale.ROOT, "%.3f ms (ref queries)", lastTimeDRF));
1963 		}else{
1964 			sb.append('\n').append("Avg time:  ").append(String.format(Locale.ROOT, "%.3f ms", avgTimeDR));
1965 			sb.append('\n').append("Last time: ").append(String.format(Locale.ROOT, "%.3f ms", lastTimeDR));
1966 			sb.append('\n').append("Avg time:  ").append(String.format(Locale.ROOT, "%.3f ms (usage queries)", avgTimeDU));
1967 			sb.append('\n').append("Last time: ").append(String.format(Locale.ROOT, "%.3f ms (usage queries)", lastTimeDU));
1968 		}
1969 		sb.append('\n');
1970 		sb.append('\n').append("Internal:  ").append(iq);
1971 		sb.append('\n').append("External:  ").append(exq);
1972 		if(!sketchOnly){sb.append('\n').append("NotFound:  ").append(nf);}
1973 		sb.append('\n');
1974 
1975 		if(sketchOnly){
1976 			sb.append('\n').append("Local:     ").append(lq);
1977 			sb.append('\n').append("Remote:    ").append(rmq);
1978 			sb.append('\n').append("Reference: ").append(rfq);
1979 			sb.append('\n');
1980 			sb.append('\n').append("Depth:     ").append(depthQueries.get());
1981 			sb.append('\n');
1982 			sb.append('\n').append("Sketches:  ").append(querySketches.get());
1983 			sb.append('\n').append("BytesIn:   ").append(bytesIn.get());
1984 			sb.append('\n').append("BytesOut:  ").append(bytesOut.get());
1985 			sb.append('\n');
1986 			sb.append('\n').append("Single:    ").append(firstChunkSingle.get());
1987 			sb.append('\n').append("Bulk:      ").append(firstChunkMulti.get());
1988 			sb.append('\n').append("UnknownS:  ").append(unknownChunkSingle.get());
1989 			sb.append('\n').append("UnknownB:  ").append(unknownChunkMulti.get());
1990 			sb.append('\n').append("Total:     ").append(bulkCount.get());
1991 		}else{
1992 			sb.append('\n').append("gi:        ").append(giQueries.get());
1993 			sb.append('\n').append("Name:      ").append(nameQueries.get());
1994 			sb.append('\n').append("TaxID:     ").append(taxidQueries.get());
1995 			sb.append('\n').append("Header:    ").append(headerQueries.get());
1996 			sb.append('\n').append("Accession: ").append(accessionQueries.get());
1997 			sb.append('\n').append("IMG:       ").append(imgQueries.get());
1998 			sb.append('\n').append("Silva:     ").append(silvaHeaderQueries.get());
1999 			sb.append('\n');
2000 			sb.append('\n').append("Simple:    ").append(simpleQueries.get());
2001 			sb.append('\n').append("Ancestor:  ").append(ancestorQueries.get());
2002 			sb.append('\n').append("Children:  ").append(childrenQueries.get());
2003 			sb.append('\n');
2004 			sb.append('\n').append("Json:      ").append(q-pt-sc-pa);
2005 			sb.append('\n').append("Plaintext: ").append(pt);
2006 			sb.append('\n').append("Semicolon: ").append(sc);
2007 			sb.append('\n').append("Path:      ").append(pa+pp);
2008 			sb.append('\n').append("Size:      ").append(ps);
2009 			sb.append('\n').append("Single:    ").append(firstChunkSingle.get());
2010 			sb.append('\n').append("Bulk:      ").append(firstChunkMulti.get());
2011 			sb.append('\n').append("Total:     ").append(bulkCount.get());
2012 		}
2013 		sb.append('\n');
2014 		return sb.toString();
2015 	}
2016 
2017 	public boolean incrementQueries(HttpExchange t, boolean local, boolean refMode, boolean simple, boolean ancestor,
2018 			boolean plaintext, boolean semicolon, boolean path, boolean printChildren, boolean printPath, boolean printSize, int type){
2019 		final boolean internal=ServerTools.isInternalQuery(t, addressPrefix, allowLocalHost, printIP, printHeaders);
2020 
2021 		if(!countQueries){return internal;}
2022 		queries.incrementAndGet();
2023 		if(local){localQueries.incrementAndGet();}
2024 		else if(refMode){localQueries.incrementAndGet();}
2025 
2026 		if(type>=0){
2027 			int type2=type&15;
2028 			if(type2==GI){
2029 				giQueries.incrementAndGet();
2030 			}else if(type2==NAME){
2031 				nameQueries.incrementAndGet();
2032 			}else if(type2==TAXID){
2033 				taxidQueries.incrementAndGet();
2034 			}else if(type2==ACCESSION){
2035 				accessionQueries.incrementAndGet();
2036 			}else if(type2==IMG){
2037 				imgQueries.incrementAndGet();
2038 			}else if(type2==HEADER){
2039 				headerQueries.incrementAndGet();
2040 			}else if(type2==UNKNOWN){
2041 				unknownQueries.incrementAndGet();
2042 			}else if(type2==SILVAHEADER){
2043 				silvaHeaderQueries.incrementAndGet();
2044 			}
2045 
2046 			if(simple){simpleQueries.incrementAndGet();}
2047 			if(ancestor){ancestorQueries.incrementAndGet();}
2048 
2049 			if(plaintext){plaintextQueries.incrementAndGet();}
2050 			else if(semicolon){semicolonQueries.incrementAndGet();}
2051 			else if(path){pathQueries.incrementAndGet();}
2052 
2053 			if(printChildren){childrenQueries.incrementAndGet();}
2054 			if(printPath){printPathQueries.incrementAndGet();}
2055 			if(printSize){printSizeQueries.incrementAndGet();}
2056 		}
2057 
2058 		if(internal){internalQueries.incrementAndGet();}
2059 
2060 		return internal;
2061 	}
2062 
2063 	/*--------------------------------------------------------------*/
2064 
2065 	String compare(ArrayList<Sketch> inSketches, DisplayParams params){
2066 		boolean success=true;
2067 		final int inSize=inSketches.size();
2068 		querySketches.addAndGet(inSize);
2069 		if(Shared.threads()<2 || maxConcurrentSketchCompareThreads<2 || inSize<4){
2070 			ByteBuilder sb=new ByteBuilder();
2071 			success=searcher.compare(inSketches, sb, params, maxConcurrentSketchCompareThreads);
2072 			return sb.toString();
2073 		}else{//More sketches than threads, and more than one thread
2074 			final int threads=Tools.min(maxConcurrentSketchCompareThreads, (inSize+4)/4);
2075 
2076 			ByteBuilder[] out=new ByteBuilder[inSize];
2077 			ArrayList<CompareThread> alct=new ArrayList<CompareThread>(threads);
2078 			AtomicInteger next=new AtomicInteger(0);
2079 			for(int i=0; i<threads; i++){
2080 				alct.add(new CompareThread(inSketches, i, next, out, params));
2081 			}
2082 			for(CompareThread ct : alct){ct.start();}
2083 			for(CompareThread ct : alct){
2084 
2085 				//Wait until this thread has terminated
2086 				while(ct.getState()!=Thread.State.TERMINATED){
2087 					try {
2088 						//Attempt a join operation
2089 						ct.join();
2090 					} catch (InterruptedException e) {
2091 						e.printStackTrace();
2092 					}
2093 				}
2094 
2095 				synchronized(ct){
2096 					success&=ct.success;
2097 				}
2098 			}
2099 			alct=null;
2100 
2101 			int len=0;
2102 			for(ByteBuilder bb : out){len=len+bb.length;}
2103 			ByteBuilder bb2=new ByteBuilder(len);
2104 			for(int i=0; i<out.length; i++){
2105 				ByteBuilder bb=out[i];
2106 				bb2.append(bb);
2107 				out[i]=null;
2108 			}
2109 			return bb2.toString();
2110 		}
2111 	}
2112 
2113 	private class CompareThread extends Thread {
2114 
2115 		CompareThread(final ArrayList<Sketch> inSketches_, final int tid_, final AtomicInteger nextSketch_, ByteBuilder[] out_, DisplayParams params_){
2116 			inSketches=inSketches_;
2117 			tid=tid_;
2118 			nextSketch=nextSketch_;
2119 			out=out_;
2120 			params=params_;
2121 		}
2122 
2123 		@Override
2124 		public void run(){
2125 			success=false;
2126 			final int inLim=inSketches.size();
2127 			final boolean json=params.json();
2128 
2129 			for(int inNum=nextSketch.getAndIncrement(); inNum<inLim; inNum=nextSketch.getAndIncrement()){
2130 				Sketch a=inSketches.get(inNum);
2131 				assert(buffer.cbs==null); //Because this sketch will only be used by one thread at a time, so per-buffer bitsets are not needed.
2132 				SketchResults sr=searcher.processSketch(a, buffer, fakeID, map, params, 1);
2133 				a.clearRefHitCounts();
2134 
2135 				ByteBuilder bb=sr.toText(params);
2136 				if(out!=null){
2137 					if(json && inLim>1){
2138 						if(inNum==0){
2139 							bb.insert(0, (byte)'[');
2140 						}
2141 						if(inNum<inLim-1){
2142 							bb.append(',');
2143 						}else{
2144 							bb.append(']');
2145 						}
2146 					}
2147 					synchronized(out){
2148 						out[inNum]=bb;
2149 					}
2150 				}
2151 			}
2152 			synchronized(this){success=true;}
2153 		}
2154 
2155 		private final ArrayList<Sketch> inSketches;
2156 		private final int tid;
2157 		private final CompareBuffer buffer=new CompareBuffer(false);
2158 		private final DisplayParams params;
2159 		private final ByteBuilder[] out;
2160 
2161 		private final AtomicInteger nextSketch;
2162 		private final AtomicInteger fakeID=new AtomicInteger(SketchObject.minFakeID);
2163 		private ConcurrentHashMap<Integer, Comparison> map=new ConcurrentHashMap<Integer, Comparison>(101);
2164 
2165 		boolean success=false;
2166 
2167 	}
2168 
2169 
2170 	/*--------------------------------------------------------------*/
2171 	/*----------------            Fields            ----------------*/
2172 	/*--------------------------------------------------------------*/
2173 
2174 	public boolean sketchOnly=false;
2175 
2176 	/*--------------------------------------------------------------*/
2177 	/*----------------           Counters           ----------------*/
2178 	/*--------------------------------------------------------------*/
2179 
2180 	private HashMap<String, StringNum> versionMap=new HashMap<String, StringNum>();
2181 	private AtomicLongArray timesByCount=new AtomicLongArray(10000);
2182 	private AtomicLongArray queryCounts=new AtomicLongArray(10000);
2183 
2184 	private AtomicLong notFound=new AtomicLong(0);
2185 	private AtomicLong queries=new AtomicLong(0);
2186 	/** Same IP address mask */
2187 	private AtomicLong internalQueries=new AtomicLong(0);
2188 	/** Local filesystem sketch */
2189 	private AtomicLong localQueries=new AtomicLong(0);
2190 	private AtomicLong refQueries=new AtomicLong(0);
2191 
2192 	private AtomicLong depthQueries=new AtomicLong(0);
2193 
2194 	private AtomicLong iconQueries=new AtomicLong(0);
2195 
2196 	private AtomicLong querySketches=new AtomicLong(0);
2197 
2198 	private AtomicLong unknownChunkSingle=new AtomicLong(0);
2199 	private AtomicLong unknownChunkMulti=new AtomicLong(0);
2200 	private AtomicLong firstChunkSingle=new AtomicLong(0);
2201 	private AtomicLong firstChunkMulti=new AtomicLong(0);
2202 	private AtomicLong nthChunkSingle=new AtomicLong(0);
2203 	private AtomicLong nthChunkMulti=new AtomicLong(0);
2204 
2205 	private AtomicLong singleQueries=new AtomicLong(0);
2206 	private AtomicLong bulkQueries=new AtomicLong(0);
2207 	private AtomicLong bulkCount=new AtomicLong(0);
2208 
2209 	private AtomicLong giQueries=new AtomicLong(0);
2210 	private AtomicLong nameQueries=new AtomicLong(0);
2211 	private AtomicLong taxidQueries=new AtomicLong(0);
2212 	private AtomicLong headerQueries=new AtomicLong(0);
2213 	private AtomicLong accessionQueries=new AtomicLong(0);
2214 	private AtomicLong imgQueries=new AtomicLong(0);
2215 	private AtomicLong unknownQueries=new AtomicLong(0);
2216 	private AtomicLong silvaHeaderQueries=new AtomicLong(0);
2217 
2218 	private AtomicLong plaintextQueries=new AtomicLong(0);
2219 	private AtomicLong semicolonQueries=new AtomicLong(0);
2220 	private AtomicLong pathQueries=new AtomicLong(0);
2221 	private AtomicLong printPathQueries=new AtomicLong(0);
2222 	private AtomicLong printSizeQueries=new AtomicLong(0);
2223 	private AtomicLong childrenQueries=new AtomicLong(0);
2224 
2225 	private AtomicLong simpleQueries=new AtomicLong(0);
2226 	private AtomicLong ancestorQueries=new AtomicLong(0);
2227 
2228 	private AtomicLong usageQueries=new AtomicLong(0);
2229 	private AtomicLong bytesIn=new AtomicLong(0);
2230 	private AtomicLong bytesOut=new AtomicLong(0);
2231 
2232 //	private AtomicLong elapsedTime=new AtomicLong(0);
2233 //	private AtomicLong timeMeasurements=new AtomicLong(0);
2234 //	private AtomicLong lastTime=new AtomicLong(0);
2235 
2236 	private AtomicLong elapsedTimeUsage=new AtomicLong(0);
2237 	private AtomicLong timeMeasurementsUsage=new AtomicLong(0);
2238 	private AtomicLong lastTimeUsage=new AtomicLong(0);
2239 
2240 	private AtomicLong elapsedTimeRemote=new AtomicLong(0);
2241 	private AtomicLong timeMeasurementsRemote=new AtomicLong(0);
2242 	private AtomicLong lastTimeRemote=new AtomicLong(0);
2243 
2244 	private AtomicLong elapsedTimeLocal=new AtomicLong(0);
2245 	private AtomicLong timeMeasurementsLocal=new AtomicLong(0);
2246 	private AtomicLong lastTimeLocal=new AtomicLong(0);
2247 
2248 	private AtomicLong elapsedTimeReference=new AtomicLong(0);
2249 	private AtomicLong timeMeasurementsReference=new AtomicLong(0);
2250 	private AtomicLong lastTimeReference=new AtomicLong(0);
2251 
2252 	private AtomicLong malformedQueries=new AtomicLong(0);
2253 
2254 	/*--------------------------------------------------------------*/
2255 	/*----------------            Params            ----------------*/
2256 	/*--------------------------------------------------------------*/
2257 
2258 	public boolean printIP=false;
2259 	public boolean printHeaders=false;
2260 	public boolean countQueries=true;
2261 	public float prealloc=0;
2262 	public boolean useHtml=false;
2263 
2264 	/** Location of GiTable file */
2265 	private String giTableFile=null;
2266 	/** Location of TaxTree file */
2267 	private String taxTreeFile="auto";
2268 	/** Comma-delimited locations of Accession files */
2269 	private String accessionFile=null;
2270 	/** Location of IMG dump file */
2271 	private String imgFile=null;
2272 	/** Location of accession pattern file */
2273 	private String patternFile=null;
2274 
2275 	private String sizeFile=null;
2276 
2277 	/** Location of sequence directory tree */
2278 	private String basePath="/global/projectb/sandbox/gaag/bbtools/tree/";
2279 
2280 	/** Used for taxonomic tree traversal */
2281 	private final TaxTree tree;
2282 
2283 	/** Maps URL Strings to numeric query types */
2284 	private final HashMap<String, Integer> typeMap;
2285 	/** Maps common organism names to scientific names */
2286 	private final HashMap<String, String> commonMap;
2287 
2288 	/** Hash taxonomic names for lookup */
2289 	private boolean hashNames=true;
2290 	private boolean hashDotFormat=true;
2291 
2292 	/** Kill code of prior server instance (optional) */
2293 	private String oldKillCode=null;
2294 	/** Address of prior server instance (optional) */
2295 	private String oldAddress=null;
2296 
2297 	/** Address of current server instance (optional) */
2298 	public String domain=null;
2299 
2300 	public int maxConcurrentSketchCompareThreads=8;//TODO: This might be too high when lots of concurrent sessions are active
2301 	public int maxConcurrentSketchLoadThreads=4;//TODO: This might be too high when lots of concurrent sessions are active
2302 	public int handlerThreads=-1;
2303 
2304 	/*--------------------------------------------------------------*/
2305 	/*----------------         Final Fields         ----------------*/
2306 	/*--------------------------------------------------------------*/
2307 
2308 	private final boolean distributed;
2309 	private final int serverNum;
2310 	private final int serverCount;
2311 	private ArrayList<String> slaveAddress;
2312 
2313 	public final String favIconPath=Data.findPath("?favicon.ico");
2314 	public final byte[] favIcon=ReadWrite.readRaw(favIconPath);
2315 
2316 	private final String startTime=new Date().toString();
2317 
2318 	/** Listen on this port */
2319 	public final int port;
2320 	/** Code to validate kill requests */
2321 	public final String killCode;
2322 
2323 	public final HttpServer httpServer;
2324 
2325 	/** Bit to set for plaintext query types */
2326 	public static final int PT_BIT=16;
2327 	/** Bit to set for semicolon-delimited query types */
2328 	public static final int SC_BIT=32;
2329 	/** Bit to set for path query types */
2330 	public static final int PA_BIT=64;
2331 	/** Request query types */
2332 	public static final int UNKNOWN=0, GI=1, NAME=2, TAXID=3, HEADER=4, ACCESSION=5, IMG=6, SILVAHEADER=7;
2333 	/** Plaintext-response query types */
2334 	public static final int PT_GI=GI+PT_BIT, PT_NAME=NAME+PT_BIT, PT_TAXID=TAXID+PT_BIT,
2335 			PT_HEADER=HEADER+PT_BIT, PT_ACCESSION=ACCESSION+PT_BIT, PT_IMG=IMG+PT_BIT, PT_SILVAHEADER=SILVAHEADER+PT_BIT;
2336 	/** Semicolon-response query types */
2337 	public static final int SC_GI=GI+SC_BIT, SC_NAME=NAME+SC_BIT, SC_TAXID=TAXID+SC_BIT,
2338 			SC_HEADER=HEADER+SC_BIT, SC_ACCESSION=ACCESSION+SC_BIT, SC_IMG=IMG+SC_BIT, SC_SILVAHEADER=SILVAHEADER+PT_BIT;
2339 
2340 	public static final int SOURCE_REFSEQ=1, SOURCE_SILVA=2, SOURCE_IMG=3;
2341 
2342 	/** Generic response when asking for tax advice */
2343 	public static final String TAX_ADVICE="This site does not give tax advice.";
2344 	/** Generic response for incorrect kill code */
2345 	public static final String BAD_CODE="Incorrect code.";
2346 	/** Generic response for badly-formatted queries */
2347 	public final String USAGE;
2348 	/** HTML version */
2349 //	public final String USAGE_HTML;
2350 	public final String rawHtml;
2351 
2352 	/** Tool for comparing query sketches to reference sketches */
2353 	public final SketchSearcher searcher=new SketchSearcher();
2354 
2355 	public final boolean hasSketches;
2356 
2357 	final boolean allowRemoteFileAccess;
2358 	final boolean allowLocalHost;
2359 	final String addressPrefix;
2360 	private boolean clearMem=true;
2361 
2362 	/*--------------------------------------------------------------*/
2363 	/*----------------        Common Fields         ----------------*/
2364 	/*--------------------------------------------------------------*/
2365 
2366 	/** Print status messages to this output stream */
2367 	private PrintStream outstream=System.err;
2368 	/** Print verbose messages */
2369 	public static boolean verbose=false, verbose2=false, logUsage=false;
2370 	/** True if an error was encountered */
2371 	public boolean errorState=false;
2372 
2373 }
2374