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}