001package org.biopax.paxtools.pattern.miner; 002 003import org.biopax.paxtools.io.SimpleIOHandler; 004import org.biopax.paxtools.model.BioPAXElement; 005import org.biopax.paxtools.model.Model; 006import org.biopax.paxtools.pattern.Match; 007import org.biopax.paxtools.pattern.Pattern; 008import org.biopax.paxtools.pattern.Searcher; 009import org.biopax.paxtools.pattern.util.Blacklist; 010import org.biopax.paxtools.pattern.util.ProgressWatcher; 011 012import javax.swing.*; 013import javax.swing.filechooser.FileNameExtensionFilter; 014import java.awt.*; 015import java.awt.event.ActionEvent; 016import java.awt.event.ActionListener; 017import java.awt.event.KeyEvent; 018import java.awt.event.KeyListener; 019import java.io.*; 020import java.lang.management.ManagementFactory; 021import java.lang.management.MemoryPoolMXBean; 022import java.lang.management.MemoryType; 023import java.net.URL; 024import java.net.URLConnection; 025import java.util.*; 026import java.util.List; 027import java.util.zip.GZIPInputStream; 028import java.util.zip.ZipInputStream; 029 030import static javax.swing.JOptionPane.*; 031 032/** 033 * This is the user interface with GUI for selecting a model, a pattern miner, and an output file. 034 * The dialog then executes the search and writes the result. 035 * 036 * @author Ozgun Babur 037 */ 038public class Dialog extends JFrame implements ActionListener, KeyListener 039{ 040 /** 041 * User specified miners to use. 042 */ 043 private Miner[] miners; 044 045 /** 046 * Checkbox for downloading and using PC data. 047 */ 048 private JRadioButton pcRadio; 049 050 /** 051 * Checkbox for downloading and using PC data. 052 */ 053 private JRadioButton customFileRadio; 054 055 /** 056 * Checkbox for downloading and using PC data. 057 */ 058 private JRadioButton customURLRadio; 059 060 /** 061 * Text fiels for model filename. 062 */ 063 private JTextField modelField; 064 065 /** 066 * Text fiels for model filename. 067 */ 068 private JTextField urlField; 069 070 /** 071 * Button for loading the model. 072 */ 073 private JButton loadButton; 074 075 /** 076 * Combo box for pattern to use. 077 */ 078 private JComboBox pcCombo; 079 080 /** 081 * Combo box for pattern to use. 082 */ 083 private JComboBox patternCombo; 084 085 /** 086 * Area for description of the pattern. 087 */ 088 private JTextArea descArea; 089 090 /** 091 * Field for output file name. 092 */ 093 private JTextField outputField; 094 095 /** 096 * Button for searching the model. 097 */ 098 private JButton runButton; 099 100 /** 101 * Text for the progress. 102 */ 103 private JLabel prgLabel; 104 105 /** 106 * The progress bar. 107 */ 108 private JProgressBar prgBar; 109 110 /** 111 * Prefix of URL of the Pathway Commons data. 112 */ 113 private static final String PC_DATA_URL_PREFIX = 114 "http://www.pathwaycommons.org/pc2/downloads/Pathway%20Commons.4."; 115// "http://webservice.baderlab.org:48080/downloads/Pathway%20Commons%202%20"; 116 117 /** 118 * Suffix of URL of the Pathway Commons data. 119 */ 120 private static final String PC_DATA_URL_SUFFIX = ".BIOPAX.owl.gz"; 121 122 /** 123 * Background color. 124 */ 125 private static final Color BACKGROUND = Color.WHITE; 126 127 /** 128 * Names of Pathway Commons resources. 129 */ 130 private static final Object[] PC_RES_NAMES = new Object[]{ 131 "All-Data", "Reactome", "NCI-PID", "HumanCyc", "PhosphoSitePlus", "PANTHER"}; 132 133 /** 134 * The URL components of the Pathway Commons resources. 135 */ 136 private static final String[] PC_RES_URL = new String[]{ 137 "All", "Reactome", "NCI_Nature", "HumanCyc", "PhosphoSitePlus", "PANTHER%20Pathway"}; 138 139 /** 140 * The name of the file for IDs of ubiquitous molecules. 141 */ 142 private static final String UBIQUE_FILE = "blacklist.txt"; 143 144 /** 145 * The url of the file for IDs of ubiquitous molecules. 146 */ 147 private static final String UBIQUE_URL = "http://www.pathwaycommons.org/pc2/downloads/blacklist.txt"; 148 149 /** 150 * Blacklist for detecting ubiquitous small molecules. 151 */ 152 private static Blacklist blacklist; 153 154 /** 155 * Runs the program showing the dialog. 156 * @param args ignored 157 */ 158 public static void main(String[] args) 159 { 160 Dialog d = new Dialog(); 161 d.setVisible(true); 162 } 163 164 /** 165 * Constructor for the dialog. 166 * 167 * @param miners a list of BioPAX pattern miners 168 * @throws HeadlessException when the initialization fails 169 */ 170 public Dialog(Miner... miners) throws HeadlessException 171 { 172 super("Pattern Miner"); 173 this.miners = miners; 174 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 175 init(); 176 } 177 178 /** 179 * Initializes GUI elements. 180 */ 181 private void init() 182 { 183 setSize(600, 400); 184 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 185 getContentPane().setLayout(new BorderLayout()); 186 getContentPane().setBackground(BACKGROUND); 187 188 JPanel modelPanel = new JPanel(new GridBagLayout()); 189 190 pcRadio = new JRadioButton("Use Pathway Commons"); 191 pcRadio.addActionListener(this); 192 pcRadio.setBackground(BACKGROUND); 193 GridBagConstraints con = new GridBagConstraints(); 194 con.gridx = 0; 195 con.gridy = 0; 196 con.anchor = GridBagConstraints.LINE_START; 197 modelPanel.add(pcRadio, con); 198 199 pcCombo = new JComboBox(PC_RES_NAMES); 200 pcCombo.setBackground(BACKGROUND); 201 con = new GridBagConstraints(); 202 con.gridx = 1; 203 con.gridy = 0; 204 con.anchor = GridBagConstraints.CENTER; 205 con.ipadx = 5; 206 modelPanel.add(pcCombo, con); 207 208 customFileRadio = new JRadioButton("Use custom file"); 209 customFileRadio.addActionListener(this); 210 customFileRadio.setBackground(BACKGROUND); 211 con = new GridBagConstraints(); 212 con.gridx = 0; 213 con.gridy = 1; 214 con.anchor = GridBagConstraints.LINE_START; 215 modelPanel.add(customFileRadio, con); 216 217 JPanel modelChooserPanel = new JPanel(new FlowLayout()); 218 modelField = new JTextField(15); 219 modelField.addKeyListener(this); 220 modelField.setEnabled(false); 221 modelChooserPanel.add(modelField); 222 loadButton = new JButton("Load"); 223 loadButton.addActionListener(this); 224 loadButton.setEnabled(false); 225 modelChooserPanel.add(loadButton); 226 modelChooserPanel.setBackground(BACKGROUND); 227 con = new GridBagConstraints(); 228 con.gridx = 1; 229 con.gridy = 1; 230 con.anchor = GridBagConstraints.CENTER; 231 modelPanel.add(modelChooserPanel, con); 232 233 customURLRadio = new JRadioButton("Use the owl at URL"); 234 customURLRadio.addActionListener(this); 235 customURLRadio.setBackground(BACKGROUND); 236 con = new GridBagConstraints(); 237 con.gridx = 0; 238 con.gridy = 2; 239 con.anchor = GridBagConstraints.LINE_START; 240 modelPanel.add(customURLRadio, con); 241 242 urlField = new JTextField(15); 243 urlField.addKeyListener(this); 244 urlField.setEnabled(false); 245 con = new GridBagConstraints(); 246 con.gridx = 1; 247 con.gridy = 2; 248 con.anchor = GridBagConstraints.LINE_START; 249 modelPanel.add(urlField, con); 250 251 ButtonGroup group = new ButtonGroup(); 252 group.add(pcRadio); 253 group.add(customFileRadio); 254 group.add(customURLRadio); 255 group.setSelected(pcRadio.getModel(), true); 256 257 modelPanel.setBorder(BorderFactory.createTitledBorder("Source model")); 258 modelPanel.setBackground(BACKGROUND); 259 260 getContentPane().add(modelPanel, BorderLayout.NORTH); 261 262 JPanel minerPanel = new JPanel(new BorderLayout()); 263 minerPanel.setBackground(BACKGROUND); 264 minerPanel.setBorder(BorderFactory.createTitledBorder("Pattern to search")); 265 JPanel comboPanel = new JPanel(new FlowLayout()); 266 comboPanel.setBackground(BACKGROUND); 267 JLabel patternLabel = new JLabel("Pattern: "); 268 patternCombo = new JComboBox(getAvailablePatterns()); 269 patternCombo.addActionListener(this); 270 patternCombo.setBackground(BACKGROUND); 271 comboPanel.add(patternLabel); 272 comboPanel.add(patternCombo); 273 minerPanel.add(comboPanel, BorderLayout.NORTH); 274 275 descArea = new JTextArea(30, 3); 276 descArea.setEditable(false); 277 descArea.setBorder(BorderFactory.createTitledBorder("Description")); 278 descArea.setText(((Miner) patternCombo.getSelectedItem()).getDescription()); 279 descArea.setLineWrap(true); 280 descArea.setWrapStyleWord(true); 281 minerPanel.add(descArea, BorderLayout.CENTER); 282 283 JPanel progressPanel = new JPanel(new GridBagLayout()); 284 progressPanel.setBackground(BACKGROUND); 285 prgLabel = new JLabel(" "); 286 prgBar = new JProgressBar(); 287 prgBar.setStringPainted(true); 288 prgBar.setVisible(false); 289 con = new GridBagConstraints(); 290 con.gridx = 0; 291 con.anchor = GridBagConstraints.CENTER; 292 con.ipady = 12; 293 con.ipadx = 10; 294 progressPanel.add(prgLabel, con); 295 con = new GridBagConstraints(); 296 con.gridx = 1; 297 con.anchor = GridBagConstraints.LINE_END; 298 progressPanel.add(prgBar, con); 299 300 minerPanel.add(progressPanel, BorderLayout.SOUTH); 301 302 getContentPane().add(minerPanel, BorderLayout.CENTER); 303 304 JPanel finishPanel = new JPanel(new BorderLayout()); 305 306 JPanel lowerPanel = new JPanel(new FlowLayout()); 307 lowerPanel.setBackground(BACKGROUND); 308 outputField = new JTextField(20); 309 outputField.setBorder(BorderFactory.createTitledBorder("Output file")); 310 outputField.addActionListener(this); 311 outputField.addKeyListener(this); 312 outputField.setText(((Miner) patternCombo.getSelectedItem()).getName() + ".txt"); 313 314 finishPanel.add(outputField, BorderLayout.WEST); 315 316 runButton = new JButton("Run"); 317 runButton.addActionListener(this); 318 finishPanel.add(runButton, BorderLayout.EAST); 319 320 JPanel bufferPanel = new JPanel(new FlowLayout()); 321 bufferPanel.setMinimumSize(new Dimension(300, 10)); 322 bufferPanel.setBackground(BACKGROUND); 323 bufferPanel.add(new JLabel(" ")); 324 finishPanel.add(bufferPanel, BorderLayout.CENTER); 325 finishPanel.setBackground(BACKGROUND); 326 327 lowerPanel.add(finishPanel); 328 329 getContentPane().add(lowerPanel, BorderLayout.SOUTH); 330 } 331 332 /** 333 * Gets the maximum memory heap size for the application. This size can be modified by passing 334 * -Xmx option to the virtual machine, like "java -Xmx5G MyClass.java". 335 * @return maximum memory heap size in megabytes 336 */ 337 private int getMaxMemory() 338 { 339 int total = 0; 340 for (MemoryPoolMXBean mpBean: ManagementFactory.getMemoryPoolMXBeans()) 341 { 342 if (mpBean.getType() == MemoryType.HEAP) 343 { 344 total += mpBean.getUsage().getMax() >> 20; 345 } 346 } 347 return total; 348 } 349 350 /** 351 * Performs interactive operations. 352 * @param e current event 353 */ 354 @Override 355 public void actionPerformed(ActionEvent e) 356 { 357 if (e.getSource() == pcRadio || 358 e.getSource() == customFileRadio || 359 e.getSource() == customURLRadio) 360 { 361 pcCombo.setEnabled(pcRadio.isSelected()); 362 modelField.setEnabled(customFileRadio.isSelected()); 363 loadButton.setEnabled(customFileRadio.isSelected()); 364 urlField.setEnabled(customURLRadio.isSelected()); 365 } 366 else if (e.getSource() == loadButton) 367 { 368 String current = modelField.getText(); 369 String initial = current.trim().length() > 0 ? current : "."; 370 371 JFileChooser fc = new JFileChooser(initial); 372 fc.setFileFilter(new FileNameExtensionFilter("BioPAX file (*.owl)", "owl")); 373 if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) 374 { 375 File file = fc.getSelectedFile(); 376 modelField.setText(file.getPath()); 377 } 378 } 379 else if (e.getSource() == patternCombo) 380 { 381 Miner m = (Miner) patternCombo.getSelectedItem(); 382 descArea.setText(m.getDescription()); 383 384 // Update output file name 385 String text = outputField.getText(); 386 if (text.contains("/")) text = text.substring(0, text.lastIndexOf("/") + 1); 387 else text = ""; 388 text += m.getName() + ".txt"; 389 outputField.setText(text); 390 } 391 else if (e.getSource() == runButton) 392 { 393 run(); 394 } 395 396 checkRunButton(); 397 } 398 399 /** 400 * Checks if the run button should be enabled. 401 */ 402 private void checkRunButton() 403 { 404 runButton.setEnabled((pcRadio.isSelected() || 405 (customFileRadio.isSelected() && !modelField.getText().trim().isEmpty()) || 406 (customURLRadio.isSelected() && !urlField.getText().trim().isEmpty())) 407 && !outputField.getText().trim().isEmpty()); 408 } 409 410 /** 411 * Listens key pressing events. Enables or disables run button. 412 * @param keyEvent event 413 */ 414 @Override 415 public void keyTyped(KeyEvent keyEvent) 416 { 417 checkRunButton(); 418 } 419 420 @Override 421 public void keyPressed(KeyEvent keyEvent){} 422 @Override 423 public void keyReleased(KeyEvent keyEvent){} 424 425 /** 426 * Gets the available pattern miners. First lists the parameter miners, then adds the known 427 * miners in the package. 428 * @return pattern miners 429 */ 430 private Object[] getAvailablePatterns() 431 { 432 List<Miner> minerList = new ArrayList<Miner>(); 433 if (miners != null && miners.length > 0) 434 { 435 minerList.addAll(Arrays.asList(miners)); 436 } 437 else 438 { 439 minerList.add(new DirectedRelationMiner()); 440 minerList.add(new ControlsStateChangeOfMiner()); 441 minerList.add(new CSCOButIsParticipantMiner()); 442 minerList.add(new CSCOBothControllerAndParticipantMiner()); 443 minerList.add(new CSCOThroughControllingSmallMoleculeMiner()); 444 minerList.add(new CSCOThroughBindingSmallMoleculeMiner()); 445 minerList.add(new ControlsStateChangeDetailedMiner()); 446 minerList.add(new ControlsPhosphorylationMiner()); 447 minerList.add(new ControlsTransportMiner()); 448 minerList.add(new ControlsExpressionMiner()); 449 minerList.add(new ControlsExpressionWithConvMiner()); 450 minerList.add(new CSCOThroughDegradationMiner()); 451 minerList.add(new ControlsDegradationIndirectMiner()); 452 minerList.add(new ConsumptionControlledByMiner()); 453 minerList.add(new ControlsProductionOfMiner()); 454 minerList.add(new CatalysisPrecedesMiner()); 455 minerList.add(new ChemicalAffectsThroughBindingMiner()); 456 minerList.add(new ChemicalAffectsThroughControlMiner()); 457 minerList.add(new ControlsTransportOfChemicalMiner()); 458 minerList.add(new InComplexWithMiner()); 459 minerList.add(new InteractsWithMiner()); 460 minerList.add(new NeighborOfMiner()); 461 minerList.add(new ReactsWithMiner()); 462 minerList.add(new UsedToProduceMiner()); 463 minerList.add(new RelatedGenesOfInteractionsMiner()); 464 minerList.add(new UbiquitousIDMiner()); 465 } 466 467 for (Miner miner : minerList) 468 { 469 if (miner instanceof MinerAdapter) ((MinerAdapter) miner).setBlacklist(blacklist); 470 } 471 472 return minerList.toArray(new Object[minerList.size()]); 473 } 474 475 /** 476 * Executes the pattern search in a new thread. 477 */ 478 private void run() 479 { 480 Thread t = new Thread(new Runnable(){public void run(){mine();}}); 481 t.start(); 482 } 483 484 /** 485 * Executes the pattern search. 486 */ 487 private void mine() 488 { 489 Miner miner = (Miner) patternCombo.getSelectedItem(); 490 if (miner instanceof MinerAdapter) 491 ((MinerAdapter) miner).setIDFetcher(new CommonIDFetcher()); 492 493 // Constructing the pattern before loading any model for a debug friendly code. Otherwise if 494 // loading model takes time and an exception occurs in pattern construction, it is just too 495 // much wait for nothing. 496 ((Miner) patternCombo.getSelectedItem()).getPattern(); 497 498 // Prepare progress bar 499 500 ProgressWatcher prg = new ProgressWatcher() 501 { 502 @Override 503 public void setTotalTicks(int total) 504 { 505 prgBar.setMaximum(total); 506 } 507 508 @Override 509 public void tick(int times) 510 { 511 prgBar.setValue(prgBar.getValue() + times); 512 } 513 }; 514 515 prgBar.setVisible(true); 516 517 // Get the model file 518 519 File modFile; 520 521 if (pcRadio.isSelected()) 522 { 523 if (getMaxMemory() < 4000) 524 { 525 showMessageDialog(this, "Maximum memory not large enough for handling\n" + 526 "Pathway Commons data. But will try anyway.\n" + 527 "Please consider running this application with the\n" + 528 "virtual machine parameter \"-Xmx5G\"."); 529 } 530 531 modFile = new File(getPCFilename()); 532 if (!modFile.exists()) 533 { 534 prgLabel.setText("Downloading model"); 535 if (!downloadPC(prg)) 536 { 537 eraseProgressBar(); 538 showMessageDialog(this, 539 "Cannot download Pathway Commons data for some reason. Sorry."); 540 return; 541 } 542 assert modFile.exists(); 543 } 544 } 545 else if (customFileRadio.isSelected()) 546 { 547 modFile = new File(modelField.getText()); 548 } 549 else if (customURLRadio.isSelected()) 550 { 551 String url = urlField.getText().trim(); 552 prgLabel.setText("Downloading model"); 553 if (url.endsWith(".gz")) downloadCompressed(prg, url, "temp.owl", true); 554 else if (url.endsWith(".zip")) downloadCompressed(prg, url, "temp.owl", false); 555 else downloadPlain(url, "temp.owl"); 556 557 modFile = new File("temp.owl"); 558 559 if (!modFile.exists()) 560 { 561 showMessageDialog(this, 562 "Cannot download the model at the given URL."); 563 eraseProgressBar(); 564 return; 565 } 566 } 567 else 568 { 569 throw new RuntimeException("Code should not be able to reach here!"); 570 } 571 572 // Get the output file 573 574 File outFile = new File(outputField.getText()); 575 576 try 577 { 578 BufferedWriter writer = new BufferedWriter(new FileWriter(outFile)); 579 writer.write("x"); 580 writer.close(); 581 outFile.delete(); 582 } 583 catch (IOException e) 584 { 585 e.printStackTrace(); 586 eraseProgressBar(); 587 showMessageDialog(this, "Cannot write to file: " + outFile.getPath()); 588 return; 589 } 590 591 // Load model 592 593 prgLabel.setText("Loading the model"); 594 prgBar.setIndeterminate(true); 595 prgBar.setStringPainted(false); 596 SimpleIOHandler io = new SimpleIOHandler(); 597 Model model; 598 599 try 600 { 601 model = io.convertFromOWL(new FileInputStream(modFile)); 602 prgBar.setIndeterminate(false); 603 prgBar.setStringPainted(true); 604 } 605 catch (FileNotFoundException e) 606 { 607 e.printStackTrace(); 608 eraseProgressBar(); 609 showMessageDialog(this, "File not found: " + modFile.getPath()); 610 return; 611 } 612 613 // Search 614 615 Miner min = (Miner) patternCombo.getSelectedItem(); 616 617 Pattern p = min.getPattern(); 618 prgLabel.setText("Searching the pattern"); 619 prgBar.setValue(0); 620 Map<BioPAXElement,List<Match>> matches = Searcher.search(model, p, prg); 621 622 if (matches.isEmpty()) 623 { 624 prgLabel.setText("No results found!"); 625 } 626 else 627 { 628 try 629 { 630 prgLabel.setText("Writing result"); 631 prgBar.setValue(0); 632 prgBar.setStringPainted(false); 633 prgBar.setIndeterminate(true); 634 FileOutputStream os = new FileOutputStream(outFile); 635 min.writeResult(matches, os); 636 os.close(); 637 prgBar.setIndeterminate(false); 638 } 639 catch (IOException e) 640 { 641 e.printStackTrace(); 642 eraseProgressBar(); 643 showMessageDialog(this, "Error occurred while writing the results"); 644 return; 645 } 646 647 prgLabel.setText("Success! "); 648 System.out.println("Success!"); 649 this.dispose(); 650 } 651 } 652 653 private void eraseProgressBar() 654 { 655 prgLabel.setText(" "); 656 prgBar.setVisible(false); 657 } 658 659 /** 660 * Gets the url for the current selected PC resource. 661 * @return the url 662 */ 663 private String getPCDataURL() 664 { 665 return PC_DATA_URL_PREFIX + PC_RES_URL[pcCombo.getSelectedIndex()] + PC_DATA_URL_SUFFIX; 666 } 667 668 /** 669 * Gets the url for the current selected PC resource. 670 * @return the url 671 */ 672 private String getPCFilename() 673 { 674 return PC_RES_NAMES[pcCombo.getSelectedIndex()].toString() + ".owl"; 675 } 676 677 /** 678 * Downloads the PC data. 679 * @return true if download successful 680 */ 681 private boolean downloadPC(ProgressWatcher prg) 682 { 683 return downloadCompressed(prg, getPCDataURL(), getPCFilename(), true); 684 } 685 686 private boolean downloadCompressed(ProgressWatcher prg, String urlString, String filename, 687 boolean gz) 688 { 689 try 690 { 691 URL url = new URL(urlString); 692 URLConnection con = url.openConnection(); 693 InputStream in = gz ? new GZIPInputStream(con.getInputStream()) : 694 new ZipInputStream(con.getInputStream()); 695 696 prg.setTotalTicks(con.getContentLength() * 8); 697 698 // Open the output file 699 OutputStream out = new FileOutputStream(filename); 700 // Transfer bytes from the compressed file to the output file 701 byte[] buf = new byte[1024]; 702 703 int lines = 0; 704 int len; 705 while ((len = in.read(buf)) > 0) 706 { 707 prg.tick(len); 708 out.write(buf, 0, len); 709 lines++; 710 } 711 712 // Close the file and stream 713 in.close(); 714 out.close(); 715 716 return lines > 0; 717 } 718 catch (IOException e) 719 { 720 e.printStackTrace(); 721 return false; 722 } 723 } 724 725 /** 726 * Downloads the PC data. 727 * @return true if download successful 728 */ 729 private static boolean downloadUbiques() 730 { 731 return downloadPlain(UBIQUE_URL, UBIQUE_FILE); 732 } 733 734 private static boolean downloadPlain(String urlString, String file) 735 { 736 try 737 { 738 URL url = new URL(urlString); 739 URLConnection con = url.openConnection(); 740 InputStream in = con.getInputStream(); 741 742 // Open the output file 743 OutputStream out = new FileOutputStream(file); 744 // Transfer bytes from the compressed file to the output file 745 byte[] buf = new byte[1024]; 746 747 int lines = 0; 748 int len; 749 while ((len = in.read(buf)) > 0) 750 { 751 out.write(buf, 0, len); 752 lines++; 753 } 754 755 // Close the file and stream 756 in.close(); 757 out.close(); 758 759 return lines > 0; 760 } 761 catch (IOException e){return false;} 762 } 763 764 /** 765 * Load ubique IDs if exists. 766 */ 767 static 768 { 769 File f = new File(UBIQUE_FILE); 770 771 if (!f.exists()) downloadUbiques(); 772 else if (f.exists()) blacklist = new Blacklist(f.getAbsolutePath()); 773 else System.out.println("Warning: Cannot load blacklist."); 774 } 775}