package nl.tudelft.lifetiles.tree.controller;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
import java.util.Set;
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
import nl.tudelft.lifetiles.core.controller.AbstractController;
import nl.tudelft.lifetiles.core.controller.MenuController;
import nl.tudelft.lifetiles.core.util.Logging;
import nl.tudelft.lifetiles.core.util.Message;
import nl.tudelft.lifetiles.core.util.Timer;
import nl.tudelft.lifetiles.sequence.model.Sequence;
import nl.tudelft.lifetiles.tree.model.PhylogeneticTreeItem;
import nl.tudelft.lifetiles.tree.model.PhylogeneticTreeParser;
import nl.tudelft.lifetiles.tree.view.SunburstView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* The controller of the tree view.
*
* @author Albert Smit
*
*/
public class TreeController extends AbstractController {
/**
* The diagram.
*/
@FXML
private SunburstView view;
/**
* the parent node.
*/
@FXML
private BorderPane wrapper;
/**
* The tree model.
*/
private PhylogeneticTreeItem tree;
/**
* The visible tree model.
*/
private PhylogeneticTreeItem visibleTree;
/**
* The model of sequences.
*/
private Map<String, Sequence> sequences;
/**
* {@inheritDoc}
*/
@Override
// checkstyle bug causes false positives, so we'll suppress them(for now).
@SuppressWarnings("checkstyle:genericwhitespace")
public void initialize(final URL location, final ResourceBundle resources) {
// load the tree when the files are opened
listen(Message.OPENED, (controller, subject, args) -> {
assert controller instanceof MenuController;
if (!"tree".equals(subject)) {
return;
}
assert args.length == 1;
assert args[0] instanceof File;
try {
loadTree((File) args[0]);
} catch (FileNotFoundException e) {
Logging.exception(e);
}
});
listen(Message.LOADED,
(controller, subject, args) -> {
if (!"sequences".equals(subject)) {
return;
}
assert args[0] instanceof Map<?, ?>;
@SuppressWarnings("unchecked")
Map<String, Sequence> newSequences = (Map<String, Sequence>) args[0];
sequences = newSequences;
repaint();
});
listen(Message.FILTERED, (controller, subject, args) -> {
// check the message is correct
assert args.length == 1;
assert args[0] instanceof Set<?>;
if (!(controller instanceof TreeController)) {
// create the new tree
@SuppressWarnings("unchecked")
Set<Sequence> newSequences = (Set<Sequence>) args[0];
setVisible(newSequences);
}
});
// inform the sunburst of this controller so filters can be shouted
view.setController(this);
view.setBounds(wrapper.layoutBoundsProperty().get());
wrapper.layoutBoundsProperty().addListener(
(observableBounds, oldValue, newValue) -> view
.setBounds(newValue));
}
/**
* Loads the tree located in the file.
*
* @param file
* The .nwk file
* @throws FileNotFoundException
* when the file is not found
*/
// simple files, so default encoding is fine. Better to keep things
// flexible with respect to what files can be parsed.
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
private void loadTree(final File file) throws FileNotFoundException {
// convert the file to a single string
String fileString = null;
Scanner scanner = new Scanner(file);
scanner.useDelimiter("\\Z");
fileString = scanner.next();
scanner.close();
// parse the string into a tree
tree = PhylogeneticTreeParser.parse(fileString);
linkSequence(sequences, tree);
tree.populateChildSequences();
visibleTree = tree;
repaint();
shout(Message.LOADED, "tree", tree);
}
/**
* Repaints the view.
*/
private void repaint() {
if (visibleTree != null) {
view.setRoot(visibleTree);
}
}
/**
* Add the sequences to a tree, a sequence will be added to a node if the
* node's name matches the sequence's id.
*
* @param sequences
* a map containing the sequences and their identifiers
* @param node
* the root of a phylogenetic tree
*/
private void linkSequence(final Map<String, Sequence> sequences,
final PhylogeneticTreeItem node) {
String ident = node.getName();
Sequence sequence = sequences.get(ident);
node.setSequence(sequence);
for (PhylogeneticTreeItem child : node.getChildren()) {
linkSequence(sequences, child);
}
}
/**
* Sets the sequences in the set to visible, and creates a tree that only
* contains the visible sequences.
*
* @param visible
* A set containing all visible sequences
*/
private void setVisible(final Set<Sequence> visible) {
Timer timer = Timer.getAndStart();
visibleTree = tree.subTree(visible);
linkSequence(sequences, visibleTree);
visibleTree.populateChildSequences();
timer.stopAndLog("creating subtree");
repaint();
}
/**
* Informs other controllers the filter changed.
*
* @param visible
* the set that needs to be visible
*/
public void shoutVisible(final Set<Sequence> visible) {
shout(Message.FILTERED, "", visible);
}
}