001 /* 002 * CRIMSON 003 * Copyright (c) 2006, Stephen Fisher, Susan Davidson, and Junhyong Kim, 004 * University of Pennsylvania. 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU General Public License as 008 * published by the Free Software Foundation; either version 2 of the 009 * License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, but 012 * WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 * 02110-1301 USA. 020 * 021 * @(#)Query.java 022 */ 023 024 package edu.upenn.crimson; 025 026 import edu.upenn.crimson.io.*; 027 import java.util.HashSet; 028 import java.util.ArrayList; 029 import java.util.StringTokenizer; 030 import java.util.Scanner; 031 import java.util.Arrays; 032 import java.sql.*; 033 import java.io.*; 034 035 /** 036 * Contains query info. 037 * 038 * @author Stephen Fisher 039 * @version $Id: Query.java,v 1.44 2009/07/25 03:37:09 fisher Exp $ 040 */ 041 042 public class Query implements Cloneable { 043 044 /** This is a unique name for the Query object. */ 045 private String id = ""; 046 047 /** Notes specific to this Query object. */ 048 private String notes = ""; 049 050 /** 051 * This is the ID for the tree object that this query refers 052 * to. 053 */ 054 private String treeID = ""; 055 056 /** 057 * LEAF SELECTION: 0 = Select All, 1 = Random Selection, 2 = 058 * Select By Temporal Depth (distributed), 3 = Select By Temporal Depth (weighted), 059 * 4 = Selection by Level (distributed), 5 = Selection by Level (weighted), 6 = Manual Selection 060 */ 061 private int leafSelection = 0; 062 063 /** 064 * Number of leaves to include when choosen randomly. */ 065 private int numLeaves = 0; 066 067 /** Temporal depth of the leaves to be selected.*/ 068 private double tempDepthThresh = 0.0; 069 070 /** Level of the leaves to be selected.*/ 071 private int levelThresh = 0; 072 073 /** Set of IDs for the leaves to include. */ 074 // TEST ***** 075 // private HashSet leaves = new HashSet(); 076 private HashSet leaves = null; 077 078 /** 079 * SEQUENCE SELECTION: 0 = Select All, 1 = Random Codon Selection, 080 * 2 = Randon Position Selection, 3 = Manual Codon Selection, 4 = 081 * Manual Position Selection, 5 = Select None 082 */ 083 private int sequenceSelection = 0; 084 085 /** 086 * Number of positions (base pairs or codons) to include when 087 * choosen randomly. 088 */ 089 private int numPositions = 0; 090 091 /** This set contains the IDs for the partitions to be queried. */ 092 // TEST ***** 093 // private HashSet partitions = new HashSet(); 094 private HashSet partitions = null; 095 096 /** 097 * When true, no data will be included in the output file (ie the 098 * partitions list will be ignored). 099 */ 100 private boolean onlyStruct = false; 101 102 /** 103 * Base pair or codon positions to include. This is only valid 104 * when a single partition is selected. 105 */ 106 // TEST ***** 107 // private HashSet positions = new HashSet(); 108 private HashSet positions = null; 109 110 /** 111 * Seed for random number generator. If -1, then will be ignored 112 * when the query is run. 113 */ 114 private long seed = -1; 115 116 /** True when the query object has been saved to the database. */ 117 private boolean saved = false; 118 119 /** Output sequence for internal node. */ 120 // private boolean incInternalSeq = false; 121 122 /** Create a new Query object. */ 123 public Query(String treeID) { 124 this(treeID, ""); 125 } 126 127 /** Create a new Query object. */ 128 public Query(String treeID, String id) throws InvalidIDException { 129 if (! setTreeID(treeID)) throw new InvalidIDException("Invalid tree ID."); 130 131 id = id.trim().toUpperCase(); 132 133 // if no ID, then create a random ID for this Query 134 if (CrimsonUtils.isEmpty(id)) { 135 id = ObjectHandles.randomQueryID("Q_"); 136 } else { 137 if (ObjectHandles.containsQuery(id)) id = ObjectHandles.randomQueryID(id + "_"); 138 } 139 140 this.id = id.toUpperCase(); 141 142 // add the query to the relevant lists 143 ObjectHandles.addQuery(this); 144 } 145 146 //-------------------------------------------------------------------------- 147 // Setters and Getters 148 149 /** 150 * Set the ID. If the new ID is the same as the current ID, then 151 * doesn't do anything. If the new ID already exists in the 152 * queryPool, then throws an exception. 153 */ 154 /* 155 public void setID(String id) throws InvalidIDException { 156 // don't do anything if new and old values are the same 157 if (this.id == id) return; 158 159 // ObjectHandles.renameQuery() will do the actual changing of 160 // the Query's id. 161 try { ObjectHandles.renameQuery(this, id); } 162 catch (InvalidIDException e) { throw e; } 163 164 this.saved = false; // potentially out of sync with db 165 } 166 */ 167 168 public String getID() { return id.toUpperCase(); } 169 170 public void setNotes(String notes) { 171 if (notes == null) notes = ""; 172 this.notes = notes; 173 174 this.saved = false; // potentially out of sync with db 175 } 176 177 public String getNotes() { return notes; } 178 179 /** Returns true if treeID set, false otherwise. */ 180 public boolean setTreeID(String treeID) { 181 if (CrimsonUtils.isEmpty(treeID)) { 182 CrimsonUtils.printError("Trying to set query treeID to an empty value."); 183 return false; 184 } 185 186 if (! Database.isOpen()) { 187 CrimsonUtils.printError("Can't create queries when no active database."); 188 return false; 189 } 190 191 // XXX WHY DID I COMPARE TO ONLINE DB WHEN I CAN JUST COMPARE TO TREEPOOL??? 192 // if (Trees.dbContains(treeID)) { 193 if (ObjectHandles.containsTree(treeID)) { 194 this.treeID = treeID.toUpperCase(); 195 this.saved = false; // potentially out of sync with db 196 return true; 197 } else { 198 CrimsonUtils.printError("Invalid tree '" + treeID + "'. Tree ID not set for query: " + id); 199 return false; 200 } 201 } 202 203 public String getTreeID() { return treeID.toUpperCase(); } 204 205 public void setLeafSelection(int leafSelection) { 206 if ((leafSelection < 0) || (leafSelection > 6)) { 207 CrimsonUtils.printError("Invalid leaf selection method, in Query."); 208 } else { 209 this.leafSelection = leafSelection; 210 } 211 212 this.saved = false; // potentially out of sync with db 213 } 214 215 public int getLeafSelection() { return leafSelection; } 216 217 public String getLeafSelectionString() { 218 switch (leafSelection) { 219 case 0: return "All"; 220 case 1: return "Random"; 221 case 2: return "Temp Depth [uniform, " + tempDepthThresh + "]"; 222 case 3: return "Temp Depth [weig, " + tempDepthThresh + "]"; 223 case 4: return "Level [uniform, " + levelThresh + "]"; 224 case 5: return "Level [weig, " + levelThresh + "]"; 225 case 6: return "Manual"; 226 default: return ""; 227 } 228 } 229 230 public void setNumLeaves(int numLeaves) { 231 if (numLeaves < 0) { 232 CrimsonUtils.printError("Invalid number of leaves, in Query."); 233 } else { 234 this.numLeaves = numLeaves; 235 } 236 237 this.saved = false; // potentially out of sync with db 238 } 239 240 public int getNumLeaves() { return numLeaves; } 241 242 public void setTempDepthThresh(double tempDepthThresh) { 243 if (tempDepthThresh < 0.0) { 244 CrimsonUtils.printError("Invalid Temporal Depth, in Query."); 245 } else { 246 this.tempDepthThresh = tempDepthThresh; 247 } 248 249 this.saved = false; // potentially out of sync with db 250 } 251 252 public double getTempDepthThresh() { return tempDepthThresh; } 253 254 public void setLevelThresh(int levelThresh) { 255 if (levelThresh < 0) { 256 CrimsonUtils.printError("Invalid Level, in Query."); 257 } else { 258 this.levelThresh = levelThresh; 259 } 260 261 this.saved = false; // potentially out of sync with db 262 } 263 264 public int getLevelThresh() { return levelThresh; } 265 266 public void setLeaves(HashSet leaves) { 267 // TEST ***** 268 // if (leaves == null) leaves = new HashSet(); 269 this.leaves = leaves; 270 271 this.saved = false; // potentially out of sync with db 272 } 273 274 public HashSet getLeaves() { 275 // TEST ***** 276 if (leaves == null) leaves = new HashSet(); 277 return leaves; 278 } 279 280 public void setSequenceSelection(int sequenceSelection) { 281 if ((sequenceSelection < 0) || (sequenceSelection > 5)) { 282 CrimsonUtils.printError("Invalid sequence selection method, in Query."); 283 } else { 284 this.sequenceSelection = sequenceSelection; 285 286 // make sure onlyStruct is consistent 287 if (sequenceSelection == 5) { 288 this.onlyStruct = true; 289 } else { 290 this.onlyStruct = false; 291 } 292 } 293 294 this.saved = false; // potentially out of sync with db 295 } 296 297 public int getSequenceSelection() { return sequenceSelection; } 298 299 public String getSequenceSelectionString() { 300 switch (sequenceSelection) { 301 case 0: return "All"; 302 case 1: return "Random Codon (" + numPositions + ")"; 303 case 2: return "Random Base Pair (" + numPositions + ")"; 304 case 3: return "Manual Codon"; 305 case 4: return "Manual Base Pair"; 306 case 5: return "None"; 307 default: return ""; 308 } 309 } 310 311 public void setNumPositions(int numPositions) { 312 if (numPositions < 0) { 313 CrimsonUtils.printError("Invalid number of positions (bp/codon), in Query."); 314 } else { 315 this.numPositions = numPositions; 316 } 317 318 this.saved = false; // potentially out of sync with db 319 } 320 321 public int getNumPositions() { return numPositions; } 322 323 public void setPartitions(HashSet partitions) { 324 // TEST ***** 325 // if (partitions == null) partitions = new HashSet(); 326 this.partitions = partitions; 327 328 this.saved = false; // potentially out of sync with db 329 } 330 331 public HashSet getPartitions() { 332 // TEST ***** 333 if (partitions == null) partitions = new HashSet(); 334 return partitions; 335 } 336 337 public void setOnlyStruct(boolean onlyStruct) { 338 this.onlyStruct = onlyStruct; 339 340 // make sure sequence selection is consistent 341 if (onlyStruct) { 342 this.sequenceSelection = 5; 343 } 344 345 this.saved = false; // potentially out of sync with db 346 } 347 348 public boolean isOnlyStruct() { return onlyStruct; } 349 350 public void setPositions(HashSet positions) { 351 // TEST ***** 352 // if (positions == null) positions = new HashSet(); 353 this.positions = positions; 354 355 this.saved = false; // potentially out of sync with db 356 } 357 358 public HashSet getPositions() { 359 // TEST ***** 360 if (positions == null) positions = new HashSet(); 361 return positions; 362 } 363 364 public void setSeed(long seed) { 365 if (seed < -1) { 366 CrimsonUtils.printError("Invalid random number seed, in Query."); 367 } else { 368 this.seed = seed; 369 } 370 371 this.saved = false; // potentially out of sync with db 372 } 373 374 public long getSeed() { return seed; } 375 376 public void setSaved(boolean saved) { this.saved = saved; } 377 378 public boolean isSaved() { return saved; } 379 380 //-------------------------------------------------------------------------- 381 // Miscellaneous Methods 382 383 /** Adds a leaf to the leaf list. */ 384 public void addLeaf(String item) { 385 // TEST ***** 386 if (leaves == null) leaves = new HashSet(); 387 leaves.add(item); 388 389 this.saved = false; // potentially out of sync with db 390 } 391 392 /** Adds a partitions to the partitions list. */ 393 public void addPartition(String item) { 394 // TEST ***** 395 if (partitions == null) partitions = new HashSet(); 396 partitions.add(item.toUpperCase()); 397 398 this.saved = false; // potentially out of sync with db 399 } 400 401 /** Adds a positions to the positions list. */ 402 public void addPosition(String item) { 403 // TEST ***** 404 if (positions == null) positions = new HashSet(); 405 positions.add(item); 406 407 this.saved = false; // potentially out of sync with db 408 } 409 410 /** Adds a ':' separated list of positions to the positions list. */ 411 public void addPositions(String items) { 412 // TEST ***** 413 if (positions == null) positions = new HashSet(); 414 415 StringTokenizer tokens = new StringTokenizer(items, ":"); 416 while (tokens.hasMoreTokens()) { 417 String token = tokens.nextToken().trim(); 418 // this won't get the condition when 0 is the beginning of 419 // a range 420 if (token.compareTo("0") == 0) 421 CrimsonUtils.printWarning("Position range begins at 1, not 0. This query will not run."); 422 positions.add(token); 423 } 424 425 this.saved = false; // potentially out of sync with db 426 } 427 428 /** 429 * Create a shallow clone (just clone the structure, not the 430 * Objects) of the existing object. This will randomly assign the 431 * clone's ID, based on the ID for the parent object. 432 */ 433 public Object clone() { 434 return clone(ObjectHandles.randomQueryID(this.id + "_")); 435 } 436 437 /** 438 * Create a shallow clone (just clone the structure, not the 439 * Objects) of the existing object. 440 * @XXX Note sure if this is correct, may need to use ".set()" 441 * methods to set the values of the clone. 442 */ 443 public Object clone(String id) { 444 Query queryItem = new Query(treeID, id); 445 queryItem.leafSelection = this.leafSelection; 446 queryItem.numLeaves = this.numLeaves; 447 queryItem.tempDepthThresh = this.tempDepthThresh; 448 queryItem.levelThresh = this.levelThresh; 449 queryItem.leaves = this.leaves; 450 queryItem.sequenceSelection = this.sequenceSelection; 451 queryItem.numPositions = this.numPositions; 452 queryItem.partitions = this.partitions; 453 queryItem.positions = this.positions; 454 queryItem.seed = this.seed; 455 queryItem.notes = this.notes; 456 queryItem.saved = false; 457 return queryItem; 458 } 459 460 /** 461 * This routine is only meant to be used by Queries when loading 462 * Queries from a database via a proxy. It may change, as 463 * needed to accommodate database loading. Queries is expected to 464 * contain the output from the following select statement: "SELECT 465 * id, tree_id, notes, leaf_select, num_leaves, temp_depth_thresh, 466 * level_thresh, seq_select, num_pos, seed, leaves, partitions, 467 * positions FROM QUERIES" 468 */ 469 public static void dbLoadProxy(StringBuilder queries) { 470 int i = 0; 471 int iStart; 472 int len = queries.length() - 1; 473 Query query; 474 String tmp; 475 while (i < len) { 476 // get query ID 477 iStart = i; 478 while ((i < len) && (queries.charAt(i) != ';')) i++; 479 tmp = queries.substring(iStart,i); 480 i++; 481 // get tree ID 482 iStart = i; 483 while ((i < len) && (queries.charAt(i) != ';')) i++; 484 query = new Query(queries.substring(iStart,i), tmp); 485 i++; 486 // get notes 487 iStart = i; 488 while ((i < len) && (queries.charAt(i) != ';')) i++; 489 query.notes = queries.substring(iStart,i); 490 i++; 491 // get leafSelection 492 iStart = i; 493 while ((i < len) && (queries.charAt(i) != ';')) i++; 494 query.leafSelection = Integer.parseInt(queries.substring(iStart,i)); 495 i++; 496 // get numLeaves 497 iStart = i; 498 while ((i < len) && (queries.charAt(i) != ';')) i++; 499 query.numLeaves = Integer.parseInt(queries.substring(iStart,i)); 500 i++; 501 // get tempDepthThresh 502 iStart = i; 503 while ((i < len) && (queries.charAt(i) != ';')) i++; 504 query.tempDepthThresh = Double.parseDouble(queries.substring(iStart,i)); 505 i++; 506 // get levelThresh 507 iStart = i; 508 while ((i < len) && (queries.charAt(i) != ';')) i++; 509 query.levelThresh = Integer.parseInt(queries.substring(iStart,i)); 510 i++; 511 // get sequenceSelection 512 iStart = i; 513 while ((i < len) && (queries.charAt(i) != ';')) i++; 514 query.sequenceSelection = Integer.parseInt(queries.substring(iStart,i)); 515 i++; 516 // get numPositions 517 iStart = i; 518 while ((i < len) && (queries.charAt(i) != ';')) i++; 519 query.numPositions = Integer.parseInt(queries.substring(iStart,i)); 520 i++; 521 // get seed 522 iStart = i; 523 while ((i < len) && (queries.charAt(i) != ';')) i++; 524 query.seed = Long.parseLong(queries.substring(iStart,i)); 525 i++; 526 527 // get leaves (CLOB) 528 iStart = i; 529 while ((i < len) && (queries.charAt(i) != ';')) i++; 530 // if not empty, then add leaves 531 if (i > (iStart + 1)) query.leaves = new HashSet(Arrays.asList(queries.substring(iStart,i).split(","))); 532 i++; 533 534 // get partitions (CLOB) 535 iStart = i; 536 while ((i < len) && (queries.charAt(i) != ';')) i++; 537 // if not empty, then add leaves 538 if (i > (iStart + 1)) query.partitions = new HashSet(Arrays.asList(queries.substring(iStart,i).split(","))); 539 i++; 540 541 // get positions (CLOB) 542 iStart = i; 543 while ((i < len) && (queries.charAt(i) != ';')) i++; 544 // if not empty, then add leaves 545 if (i > (iStart + 1)) query.positions = new HashSet(Arrays.asList(queries.substring(iStart,i).split(","))); 546 i++; 547 548 // since we just downloaded the query, we know it's saved 549 query.saved = true; 550 } 551 } 552 553 /** 554 * This routine is only meant to be used by Queries when loading 555 * Queries from a database without using a proxy (ie CLOBs are not 556 * strings). It may change, as needed to accommodate database 557 * loading. qObj is expected to contain the output from the 558 * following select statement: "SELECT id, tree_id, notes, 559 * leaf_select, num_leaves, temp_depth_thresh, level_thresh, 560 * seq_select, num_pos, seed, leaves, partitions, positions FROM 561 * QUERIES" 562 */ 563 public static Object dbLoad(ArrayList qObj) { 564 Query query = new Query(String.valueOf(qObj.get(1)), String.valueOf(qObj.get(0))); 565 query.notes = String.valueOf(qObj.get(2)); 566 query.leafSelection = Integer.parseInt(String.valueOf(qObj.get(3))); 567 query.numLeaves = Integer.parseInt(String.valueOf(qObj.get(4))); 568 query.tempDepthThresh = Double.parseDouble(String.valueOf(qObj.get(5))); 569 query.levelThresh = Integer.parseInt(String.valueOf(qObj.get(6))); 570 query.sequenceSelection = Integer.parseInt(String.valueOf(qObj.get(7))); 571 query.numPositions = Integer.parseInt(String.valueOf(qObj.get(8))); 572 query.seed = Long.parseLong(String.valueOf(qObj.get(9))); 573 574 try { 575 query.setLeaves(Database.readClobSet(qObj.get(10))); 576 query.setPartitions(Database.readClobSet(qObj.get(11))); 577 query.setPositions(Database.readClobSet(qObj.get(12))); 578 /* 579 if (! CrimsonUtils.isEmpty(qObj.get(10).toString())) { 580 Clob clob = (Clob) qObj.get(10); 581 //if ((clob != null) && (clob.length() > 0)) { 582 // TEST ***** 583 query.leaves = new HashSet(); 584 StringBuffer sb = new StringBuffer(); 585 Reader reader = clob.getCharacterStream(); 586 BufferedReader bReader = new BufferedReader(reader); 587 String line; 588 while ((line = bReader.readLine()) != null) sb.append(line); 589 // query.leaves = new HashSet(Arrays.asList(sb.toString().split(","))); 590 Scanner scanner = new Scanner(sb.toString()).useDelimiter(","); 591 while (scanner.hasNext()) query.leaves.add(scanner.next().trim()); 592 593 // close the reader 594 bReader.close(); 595 reader.close(); 596 } 597 598 if (! CrimsonUtils.isEmpty(qObj.get(11).toString())) { 599 Clob clob = (Clob) qObj.get(11); 600 //if ((clob != null) && (clob.length() > 0)) { 601 // TEST ***** 602 query.partitions = new HashSet(); 603 StringBuffer sb = new StringBuffer(); 604 Reader reader = clob.getCharacterStream(); 605 BufferedReader bReader = new BufferedReader(reader); 606 String line; 607 while ((line = bReader.readLine()) != null) sb.append(line); 608 Scanner scanner = new Scanner(sb.toString()).useDelimiter(","); 609 while (scanner.hasNext()) query.partitions.add(scanner.next().trim()); 610 611 // close the reader 612 bReader.close(); 613 reader.close(); 614 } 615 616 if (! CrimsonUtils.isEmpty(qObj.get(12).toString())) { 617 Clob clob = (Clob) qObj.get(12); 618 //if ((clob != null) && (clob.length() > 0)) { 619 // TEST ***** 620 query.positions = new HashSet(); 621 StringBuffer sb = new StringBuffer(); 622 Reader reader = clob.getCharacterStream(); 623 BufferedReader bReader = new BufferedReader(reader); 624 String line; 625 while ((line = bReader.readLine()) != null) sb.append(line); 626 Scanner scanner = new Scanner(sb.toString()).useDelimiter(","); 627 while (scanner.hasNext()) query.positions.add(scanner.next().trim()); 628 629 // close the reader 630 bReader.close(); 631 reader.close(); 632 } 633 */ 634 } catch (Exception e) { 635 CrimsonUtils.printError("Error loading Query CLOBs."); 636 CrimsonUtils.printError(e.getMessage()); 637 return null; 638 } 639 640 // since we just downloaded the query, we know it's saved 641 query.saved = true; 642 643 return query; 644 } 645 646 /** 647 * Creates an array with the fields in the following order: id, 648 * leafSelection, numLeaves, tempDepthThresh, levelThresh, leaves, 649 * sequenceSelection, numPositions, partitions, positions, 650 * seed, treeID, notes. 651 */ 652 /* 653 public Object[] toArray() { 654 Object[] out = new Object[12]; 655 out[0] = id; 656 out[1] = Integer.toString(leafSelection); 657 out[2] = Integer.toString(numLeaves); 658 out[3] = Double.toString(tempDepthThresh); 659 out[4] = Integer.toString(levelThresh); 660 out[5] = leaves; 661 out[6] = Integer.toString(sequenceSelection); 662 out[7] = Integer.toString(numPositions); 663 out[8] = partitions; 664 out[9] = positions; 665 out[10] = Double.toString(seed); 666 out[11] = treeID; 667 out[12] = notes; 668 return out; 669 } 670 */ 671 672 /** Returns Query information for debugging purposes. */ 673 public String toString() { 674 String out = ""; 675 out += " ID: " + id + "\n"; 676 out += " Tree ID: " + treeID + "\n"; 677 out += " Leaf Selection: " + getLeafSelectionString() + "\n"; 678 out += " Number Leaves: " + Integer.toString(numLeaves) + "\n"; 679 out += " Temporal Depth Thresh: " + Double.toString(tempDepthThresh) + "\n"; 680 out += " Level Thresh: " + Integer.toString(levelThresh) + "\n"; 681 out += " Leaves: " + leaves + "\n"; 682 out += " Sequence Selection: " + getSequenceSelectionString() + "\n"; 683 out += " Number Positions (bp/codon): " + Integer.toString(numPositions) + "\n"; 684 out += " Partitions: " + partitions + "\n"; 685 out += " Positions (bp/codon): " + positions + "\n"; 686 out += " Seed: " + Long.toString(seed) + "\n"; 687 out += " Notes: " + notes + "\n"; 688 out += " Saved: " + saved + "\n"; 689 return out; 690 } 691 } // Query.java