package beast.app.beauti;
import java.awt.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import beast.app.util.Utils;
import beast.core.util.Log;
public class GuessPatternDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static String TRAIT_FILE_HELP_MESSAGE =
"This option allows trait values (such as species, tip dates and sample locations) " +
"to be specified using a file which links each taxon " +
"with a trait value. The file must contain one row per " +
"taxon, with each row containing the taxon name and the " +
"trait value separated by a TAB (not a space!) character.\n" +
"\n" +
"For instance, a file specifying the ages of three taxa named " +
"taxonA, taxonB and taxonC should contain the following:\n" +
"\n" +
"taxonA 0.0\n" +
"taxonB 0.1\n" +
"taxonC 0.2\n" +
"\n" +
"where the gap between each taxon name and the numeric value " +
"representing the age must be a TAB.";
public enum Status {
canceled, pattern, trait
};
public String trait = null;
public String getTrait() {
return trait;
}
Component m_parent;
JPanel guessPanel;
ButtonGroup group;
JRadioButton useEverything = new JRadioButton("use everything");
JRadioButton isSplitOnChar = new JRadioButton("split on character");
JRadioButton useRegexp = new JRadioButton("use regular expression");
JRadioButton readFromFile = new JRadioButton("read from file");
int m_location = 0;
int m_splitlocation = 0;
String m_sDelimiter = ".";
JTextField textRegExp;
JComboBox<String> combo;
JComboBox<String> combo_1;
String pattern;
public String getPattern() {
return pattern;
}
private JTextField txtFile;
private JTextField textSplitChar;
private JTextField textSplitChar2;
private JTextField textAddValue;
private JTextField textUnlessLessThan;
private JTextField textThenAdd;
JCheckBox chckbxAddFixedValue;
JCheckBox chckbxUnlessLessThan;
JLabel lblThenAdd;
JLabel lblAndTakeGroups;
JButton btnBrowse;
private JSeparator separator_2;
private JSeparator separator_3;
private JSeparator separator_4;
private JSeparator separator_5;
public GuessPatternDialog(Component parent, String pattern) {
m_parent = parent;
this.pattern = pattern;
guessPanel = new JPanel();
GridBagLayout gbl_guessPanel = new GridBagLayout();
gbl_guessPanel.rowHeights = new int[]{0, 0, 0, 20, 0, 0, 20, 0, 0, 20, 0, 29, 0, 0, 0, 0};
gbl_guessPanel.columnWidths = new int[]{0, 0, 0, 0, 0, 0};
gbl_guessPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
gbl_guessPanel.columnWeights = new double[] { 1.0, 1.0, 1.0, 0.0, 1.0, 0.0 };
guessPanel.setLayout(gbl_guessPanel);
group = new ButtonGroup();
group.add(useEverything);
group.add(isSplitOnChar);
group.add(useRegexp);
group.add(readFromFile);
group.setSelected(useEverything.getModel(), true);
useEverything.addActionListener(e -> {
updateFields();
});
useEverything.setName(useEverything.getText());
isSplitOnChar.addActionListener(e -> {
updateFields();
});
isSplitOnChar.setName(isSplitOnChar.getText());
useRegexp.addActionListener(e -> {
updateFields();
});
useRegexp.setName(useRegexp.getText());
readFromFile.addActionListener(e -> {
updateFields();
});
readFromFile.setName(readFromFile.getText());
createDelimiterBox(useEverything);
createSplitBox(isSplitOnChar);
createRegExtpBox(useRegexp);
textRegExp = new JTextField();
textRegExp.setText(pattern);
textRegExp.setColumns(10);
textRegExp.setToolTipText("Enter regular expression to match taxa");
int fontsize = textRegExp.getFont().getSize();
textRegExp.setMaximumSize(new Dimension(1024 * fontsize/13, 25 * fontsize/13));
GridBagConstraints gbc2 = new GridBagConstraints();
gbc2.insets = new Insets(0, 0, 5, 5);
gbc2.anchor = GridBagConstraints.WEST;
gbc2.gridwidth = 4;
gbc2.gridx = 1;
gbc2.gridy = 7;
guessPanel.add(textRegExp, gbc2);
textRegExp.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
useRegexp.setSelected(true);
}
@Override
public void insertUpdate(DocumentEvent e) {
useRegexp.setSelected(true);
}
@Override
public void changedUpdate(DocumentEvent e) {
useRegexp.setSelected(true);
}
});
separator_4 = new JSeparator();
separator_4.setPreferredSize(new Dimension(5,1));
GridBagConstraints gbc_separator_4 = new GridBagConstraints();
gbc_separator_4.gridwidth = 5;
gbc_separator_4.insets = new Insets(5, 0, 15, 5);
gbc_separator_4.gridx = 0;
gbc_separator_4.gridy = 8;
gbc_separator_4.fill = GridBagConstraints.HORIZONTAL;
guessPanel.add(separator_4, gbc_separator_4);
GridBagConstraints gbc_rdbtnReadFromFile = new GridBagConstraints();
gbc_rdbtnReadFromFile.anchor = GridBagConstraints.WEST;
gbc_rdbtnReadFromFile.insets = new Insets(0, 0, 5, 5);
gbc_rdbtnReadFromFile.gridx = 0;
gbc_rdbtnReadFromFile.gridy = 10;
guessPanel.add(readFromFile, gbc_rdbtnReadFromFile);
btnBrowse = new JButton("Browse");
btnBrowse.addActionListener(e -> {
File file = Utils.getLoadFile("Load trait from file", new File(Beauti.g_sDir), "Select trait file", "dat","txt");
if (file != null) {
txtFile.setText(file.getPath());
readFromFile.setSelected(true);
updateFields();
}
});
txtFile = new JTextField();
txtFile.setText("File");
GridBagConstraints gbc_txtFile = new GridBagConstraints();
gbc_txtFile.gridwidth = 2;
gbc_txtFile.insets = new Insets(0, 0, 5, 5);
gbc_txtFile.fill = GridBagConstraints.HORIZONTAL;
gbc_txtFile.gridx = 1;
gbc_txtFile.gridy = 10;
guessPanel.add(txtFile, gbc_txtFile);
txtFile.setColumns(10);
GridBagConstraints gbc_btnReadFromFile = new GridBagConstraints();
gbc_btnReadFromFile.insets = new Insets(0, 0, 5, 5);
gbc_btnReadFromFile.gridx = 3;
gbc_btnReadFromFile.gridy = 10;
guessPanel.add(btnBrowse, gbc_btnReadFromFile);
JButton btnHelp = new JButton("?");
btnHelp.setToolTipText("Show format of trait file");
btnHelp.addActionListener(e -> {
JOptionPane pane = new JOptionPane() {
@Override
public int getMaxCharactersPerLineCount() {
return 70;
}
};
pane.setMessage(TRAIT_FILE_HELP_MESSAGE);
pane.setMessageType(JOptionPane.INFORMATION_MESSAGE);
JDialog dialog = pane.createDialog(this, "Message");
dialog.setModal(true);
dialog.setVisible(true);
});
GridBagConstraints gbc_btnHelp = new GridBagConstraints();
gbc_btnHelp.insets = new Insets(0, 0, 5, 5);
gbc_btnHelp.gridx = 4;
gbc_btnHelp.gridy = 10;
guessPanel.add(btnHelp, gbc_btnHelp);
chckbxAddFixedValue = new JCheckBox("Add fixed value");
chckbxAddFixedValue.setName("Add fixed value");
chckbxAddFixedValue.addActionListener(e -> {
updateFields();
});
separator_5 = new JSeparator();
separator_5.setPreferredSize(new Dimension(5,1));
GridBagConstraints gbc_separator_5 = new GridBagConstraints();
gbc_separator_5.gridwidth = 5;
gbc_separator_5.insets = new Insets(5, 0, 15, 5);
gbc_separator_5.gridx = 0;
gbc_separator_5.gridy = 12;
gbc_separator_5.fill = GridBagConstraints.HORIZONTAL;
guessPanel.add(separator_5, gbc_separator_5);
GridBagConstraints gbc_chckbxAddFixedValue = new GridBagConstraints();
gbc_chckbxAddFixedValue.anchor = GridBagConstraints.WEST;
gbc_chckbxAddFixedValue.insets = new Insets(0, 0, 5, 5);
gbc_chckbxAddFixedValue.gridx = 0;
gbc_chckbxAddFixedValue.gridy = 13;
guessPanel.add(chckbxAddFixedValue, gbc_chckbxAddFixedValue);
textAddValue = new JTextField("1900");
GridBagConstraints gbc_textField = new GridBagConstraints();
gbc_textField.gridwidth = 2;
gbc_textField.insets = new Insets(0, 0, 5, 5);
gbc_textField.fill = GridBagConstraints.HORIZONTAL;
gbc_textField.gridx = 1;
gbc_textField.gridy = 13;
guessPanel.add(textAddValue, gbc_textField);
textAddValue.setColumns(10);
chckbxUnlessLessThan = new JCheckBox("Unless less than...");
chckbxUnlessLessThan.setName("Unless less than");
chckbxUnlessLessThan.addActionListener(e -> {
updateFields();
});
GridBagConstraints gbc_chckbxUnlessLargerThan = new GridBagConstraints();
gbc_chckbxUnlessLargerThan.anchor = GridBagConstraints.WEST;
gbc_chckbxUnlessLargerThan.insets = new Insets(0, 0, 5, 5);
gbc_chckbxUnlessLargerThan.gridx = 0;
gbc_chckbxUnlessLargerThan.gridy = 14;
guessPanel.add(chckbxUnlessLessThan, gbc_chckbxUnlessLargerThan);
textUnlessLessThan = new JTextField("13");
GridBagConstraints gbc_textField_1 = new GridBagConstraints();
gbc_textField_1.gridwidth = 2;
gbc_textField_1.insets = new Insets(0, 0, 5, 5);
gbc_textField_1.fill = GridBagConstraints.HORIZONTAL;
gbc_textField_1.gridx = 1;
gbc_textField_1.gridy = 14;
guessPanel.add(textUnlessLessThan, gbc_textField_1);
textUnlessLessThan.setColumns(10);
lblThenAdd = new JLabel("...then add");
GridBagConstraints gbc_lblThenAdd = new GridBagConstraints();
gbc_lblThenAdd.anchor = GridBagConstraints.EAST;
gbc_lblThenAdd.insets = new Insets(0, 0, 0, 5);
gbc_lblThenAdd.gridx = 0;
gbc_lblThenAdd.gridy = 15;
guessPanel.add(lblThenAdd, gbc_lblThenAdd);
textThenAdd = new JTextField("2000");
GridBagConstraints gbc_textField_2 = new GridBagConstraints();
gbc_textField_2.gridwidth = 2;
gbc_textField_2.insets = new Insets(0, 0, 0, 5);
gbc_textField_2.fill = GridBagConstraints.HORIZONTAL;
gbc_textField_2.gridx = 1;
gbc_textField_2.gridy = 15;
guessPanel.add(textThenAdd, gbc_textField_2);
textThenAdd.setColumns(10);
chckbxAddFixedValue.setVisible(false);
textAddValue.setVisible(false);
chckbxUnlessLessThan.setVisible(false);
lblThenAdd.setVisible(false);
chckbxUnlessLessThan.setVisible(false);
textUnlessLessThan.setVisible(false);
textThenAdd.setVisible(false);
}
public void allowAddingValues() {
chckbxAddFixedValue.setVisible(true);
textAddValue.setVisible(true);
chckbxUnlessLessThan.setVisible(true);
lblThenAdd.setVisible(true);
chckbxUnlessLessThan.setVisible(true);
textUnlessLessThan.setVisible(true);
textThenAdd.setVisible(true);
}
protected void updateFields() {
if (chckbxAddFixedValue.isSelected()) {
textAddValue.setEnabled(true);
chckbxUnlessLessThan.setEnabled(true);
lblThenAdd.setEnabled(true);
if (chckbxUnlessLessThan.isSelected()) {
textUnlessLessThan.setEnabled(true);
textThenAdd.setEnabled(true);
} else {
textUnlessLessThan.setEnabled(false);
textThenAdd.setEnabled(false);
}
} else {
textAddValue.setEnabled(false);
chckbxUnlessLessThan.setEnabled(false);
lblThenAdd.setEnabled(false);
textUnlessLessThan.setEnabled(false);
textThenAdd.setEnabled(false);
}
txtFile.setEnabled(false);
textSplitChar.setEnabled(false);
textSplitChar2.setEnabled(false);
textRegExp.setEnabled(false);
combo.setEnabled(false);
combo_1.setEnabled(false);
lblAndTakeGroups.setEnabled(false);
btnBrowse.setEnabled(false);
if (useEverything.isSelected()) {
textSplitChar.setEnabled(true);
combo.setEnabled(true);
}
if (isSplitOnChar.isSelected()) {
textSplitChar2.setEnabled(true);
combo_1.setEnabled(true);
lblAndTakeGroups.setEnabled(true);
}
if (useRegexp.isSelected()) {
textRegExp.setEnabled(true);
}
if (readFromFile.isSelected()) {
btnBrowse.setEnabled(true);
txtFile.setEnabled(true);
}
}
private void createDelimiterBox(JRadioButton b) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 0;
gbc.gridy = 1;
guessPanel.add(b, gbc);
combo = new JComboBox<>(new String[] { "after first", "after last", "before first", "before last" });
combo.setName("delimiterCombo");
GridBagConstraints gbc2 = new GridBagConstraints();
gbc2.anchor = GridBagConstraints.WEST;
gbc2.gridwidth = 2;
gbc2.insets = new Insets(0, 0, 5, 5);
gbc2.gridx = 1;
gbc2.gridy = 1;
guessPanel.add(combo, gbc2);
combo.addActionListener(e -> {
@SuppressWarnings("unchecked")
JComboBox<String> combo = (JComboBox<String>) e.getSource();
m_location = combo.getSelectedIndex();
useEverything.setSelected(true);
updateFields();
});
}
private void createSplitBox(JRadioButton b) {
textSplitChar = new JTextField("_");
textSplitChar.setName("SplitChar");
GridBagConstraints gbc_textField = new GridBagConstraints();
gbc_textField.anchor = GridBagConstraints.WEST;
gbc_textField.insets = new Insets(0, 0, 5, 5);
gbc_textField.gridx = 3;
gbc_textField.gridy = 1;
guessPanel.add(textSplitChar, gbc_textField);
textSplitChar.setColumns(2);
separator_2 = new JSeparator();
separator_2.setPreferredSize(new Dimension(5,1));
GridBagConstraints gbc_separator_2 = new GridBagConstraints();
gbc_separator_2.gridwidth = 5;
gbc_separator_2.insets = new Insets(5, 0, 15, 5);
gbc_separator_2.gridx = 0;
gbc_separator_2.gridy = 2;
gbc_separator_2.fill = GridBagConstraints.HORIZONTAL;
guessPanel.add(separator_2, gbc_separator_2);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 0;
gbc.gridy = 4;
guessPanel.add(b, gbc);
}
public void createRegExtpBox(JRadioButton b) {
textSplitChar2 = new JTextField("_");
textSplitChar2.setName("SplitChar2");
GridBagConstraints gbc_textField_1 = new GridBagConstraints();
gbc_textField_1.anchor = GridBagConstraints.WEST;
gbc_textField_1.insets = new Insets(0, 0, 5, 5);
gbc_textField_1.gridx = 1;
gbc_textField_1.gridy = 4;
guessPanel.add(textSplitChar2, gbc_textField_1);
textSplitChar2.setColumns(2);
lblAndTakeGroups = new JLabel("and take group(s):");
GridBagConstraints gbc_lblAndTakeGroups = new GridBagConstraints();
gbc_lblAndTakeGroups.gridwidth = 2;
gbc_lblAndTakeGroups.insets = new Insets(0, 0, 5, 5);
gbc_lblAndTakeGroups.gridx = 2;
gbc_lblAndTakeGroups.gridy = 4;
guessPanel.add(lblAndTakeGroups, gbc_lblAndTakeGroups);
combo_1 = new JComboBox<>(new String[] { "1", "2", "3", "4", "1-2", "2-3", "3-4", "1-3", "2-4" });
combo_1.setName("splitCombo");
GridBagConstraints gbc_combo_1 = new GridBagConstraints();
gbc_combo_1.anchor = GridBagConstraints.WEST;
gbc_combo_1.insets = new Insets(0, 0, 5, 5);
gbc_combo_1.gridx = 4;
gbc_combo_1.gridy = 4;
guessPanel.add(combo_1, gbc_combo_1);
combo_1.addActionListener(e -> {
@SuppressWarnings("unchecked")
JComboBox<String> combo = (JComboBox<String>) e.getSource();
m_splitlocation = combo.getSelectedIndex();
isSplitOnChar.setSelected(true);
updateFields();
});
separator_3 = new JSeparator();
separator_3.setPreferredSize(new Dimension(5,1));
GridBagConstraints gbc_separator_3 = new GridBagConstraints();
gbc_separator_3.gridwidth = 5;
gbc_separator_3.insets = new Insets(5, 0, 15, 5);
gbc_separator_3.gridx = 0;
gbc_separator_3.gridy = 5;
gbc_separator_3.fill = GridBagConstraints.HORIZONTAL;
guessPanel.add(separator_3, gbc_separator_3);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx = 0;
gbc.gridy = 7;
guessPanel.add(b, gbc);
}
public Status showDialog(String title) {
JOptionPane optionPane = new JOptionPane(guessPanel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION,
null, new String[] { "Cancel", "OK" }, "OK");
optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));
final JDialog dialog = optionPane.createDialog(m_parent, title);
dialog.setName("GuessTaxonSets");
// dialog.setResizable(true);
dialog.pack();
updateFields();
dialog.setVisible(true);
if (optionPane.getValue() == null || !optionPane.getValue().equals("OK")) {
return Status.canceled;
}
if (useEverything.getModel() == group.getSelection()) {
String delimiter = normalise(textSplitChar.getText());
switch (m_location) {
case 0: // "after first",
pattern = "^[^" + delimiter + "]+" + delimiter + "(.*)$";
break;
case 1: // "after last",
pattern = "^.*" + delimiter + "(.*)$";
break;
case 2: // "before first",
pattern = "^([^" + delimiter + "]+)" + delimiter + ".*$";
break;
case 3: // "before last"
pattern = "^(.*)" + delimiter + ".*$";
break;
}
}
if (isSplitOnChar.getModel() == group.getSelection()) {
String delimiter = normalise(textSplitChar2.getText());
switch (m_splitlocation) {
case 0: // "1"
pattern = "^([^" + delimiter + "]+)" + ".*$";
break;
case 1: // "2"
pattern = "^[^" + delimiter + "]+" + delimiter + "([^" + delimiter + "]+)" + ".*$";
break;
case 2: // "3"
pattern = "^[^" + delimiter + "]+" + delimiter + "[^" + delimiter + "]+" + delimiter + "([^"
+ delimiter + "]+)" + ".*$";
break;
case 3: // "4"
pattern = "^[^" + delimiter + "]+" + delimiter + "[^" + delimiter + "]+" + delimiter + "[^"
+ delimiter + "]+" + delimiter + "([^" + delimiter + "]+)" + ".*$";
break;
case 4: // "1-2"
pattern = "^([^" + delimiter + "]+" + delimiter + "[^" + delimiter + "]+)" + ".*$";
break;
case 5: // "2-3"
pattern = "^[^" + delimiter + "]+" + delimiter + "([^" + delimiter + "]+" + delimiter + "[^"
+ delimiter + "]+)" + ".*$";
break;
case 6: // "3-4"
pattern = "^[^" + delimiter + "]+" + delimiter + "[^" + delimiter + "]+" + delimiter + "([^"
+ delimiter + "]+" + delimiter + "[^" + delimiter + "]+)" + ".*$";
break;
case 7: // "1-3"
pattern = "^([^" + delimiter + "]+" + delimiter + "[^" + delimiter + "]+" + delimiter + "[^"
+ delimiter + "]+)" + ".*$";
break;
case 8: // "2-4"
pattern = "^[^" + delimiter + "]+" + delimiter + "([^" + delimiter + "]+" + delimiter + "[^"
+ delimiter + "]+" + delimiter + "[^" + delimiter + "]+)" + ".*$";
}
}
if (useRegexp.getModel() == group.getSelection()) {
pattern = textRegExp.getText();
}
if (readFromFile.getModel() == group.getSelection()) {
try {
BufferedReader fin = new BufferedReader(new FileReader(txtFile.getText()));
StringBuffer buf = new StringBuffer();
// do not eat up header -- it might contain a useful entry,
// but if not, it will not hurt
// fin.readLine();
// process data
while (fin.ready()) {
String str = fin.readLine();
str = str.replaceFirst("\t", "=") + ",";
// only add entries that are non-empty
if (str.indexOf("=") > 0 && !str.matches("^\\s+=.*$")) {
buf.append(str);
}
}
fin.close();
trait = buf.toString().trim();
while (trait.endsWith(",")) {
trait = trait.substring(0, trait.length() - 1).trim();
}
if (trait.trim().length() == 0) {
JOptionPane.showMessageDialog(m_parent, "Could not find trait information in the file. " +
"Perhaps this is not a tab-delimited but space file?");
}
} catch (Exception e) {
JOptionPane.showMessageDialog(m_parent, "Loading trait from file failed:" + e.getMessage());
return Status.canceled;
}
return Status.trait;
}
// sanity check
try {
pattern.matches(pattern);
} catch (PatternSyntaxException e) {
JOptionPane.showMessageDialog(this, "This is not a valid regular expression");
return Status.canceled;
}
if (optionPane.getValue() != null && optionPane.getValue().equals("OK")) {
Log.warning.println("Pattern = " + pattern);
return Status.pattern;
} else {
return Status.canceled;
}
}
/**
* Converts the first character of delimiter into a substring suitable for
* inclusion in a regexp. This is done by expressing the character as an
* octal escape.
*
* @param delimiter first character of this string to be used as delimiter
* @return escaped octal representation of character
*/
private String normalise(String delimiter) {
if (delimiter.length() == 0) {
return ".";
}
return String.format("\\0%o", (int)delimiter.charAt(0));
}
public String match(String s) {
Pattern _pattern = Pattern.compile(pattern);
Matcher matcher = _pattern.matcher(s);
if (matcher.find()) {
String match = matcher.group(1);
if (chckbxAddFixedValue.isSelected()) {
try {
Double value = Double.parseDouble(match);
Double addValue = Double.parseDouble(textAddValue.getText());
if (chckbxUnlessLessThan.isSelected()) {
Double threshold = Double.parseDouble(textUnlessLessThan.getText());
Double addValue2 = Double.parseDouble(textThenAdd.getText());
if (value < threshold) {
value += addValue2;
} else {
value += addValue;
}
} else {
value += addValue;
}
return value + "";
} catch (Exception e) {
// ignore
}
}
return match;
}
return null;
}
}