package beast.app.beauti;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import beast.app.draw.BEASTObjectPanel;
import beast.app.draw.InputEditor;
import beast.core.BEASTInterface;
import beast.core.BEASTObject;
import beast.core.Description;
import beast.core.Input;
import beast.core.Input.Validate;
import beast.core.util.Log;
@Description("Defines properties for custom panels in Beauti")
public class BeautiPanelConfig extends BEASTObject {
public enum Partition {
none, Partition, SiteModel, ClockModel, Tree
}
final public Input<String> nameInput = new Input<>("panelname", "name of the panel, used to label the panel and in the visibility menu", Validate.REQUIRED);
final public Input<String> tipTextInput = new Input<>("tiptext", "tiptext shown when hovering over the tab", Validate.REQUIRED);
final public Input<String> pathInput = new Input<>("path", "path of the BEASTObject to be shown in this panel, in xpath-like format. " +
"For example operator to edit the operator input of the top level run element. " +
"distribution/distribution[id='prior'] for prior distributions." +
"distribution/distribution[id='posterior']/traitset all posterior inputs with name traitset", Validate.REQUIRED);
final public Input<Partition> hasPartitionsInput = new Input<>("hasPartitions", "flag to indicate the panel has" +
"a partition context (and hence a partition list), deafult none. Possible values: " + Arrays.toString(Partition.values()), Partition.none, Partition.values());
final public Input<Boolean> addButtonsInput = new Input<>("addButtons", "flag to indicate buttons should be added, default true", true);
final public Input<Boolean> isVisibleInput = new Input<>("isVisible", "flag to indicate panel is visible on startup, default true", true);
final public Input<String> iconInput = new Input<>("icon", "icon shown in the panel relative to /beast/app/beauti, default 0.png", "0.png");
final public Input<InputEditor.ExpandOption> forceExpansionInput = new Input<>("forceExpansion", "whether to expand the input(s)" +
"This can be " + Arrays.toString(InputEditor.ExpandOption.values()) + " (default 'FALSE')", InputEditor.ExpandOption.FALSE, InputEditor.ExpandOption.values());
final public Input<String> typeInput = new Input<>("type", "type used for finding the appropriate beastObject editor. By default, type is determined " +
"by the input type of the last component of the path");
final public Input<Integer> labelWidthInput = new Input<>("labelWidth", "width of labels used to show name of inputs in input editors", 150);
final public Input<InputEditor.ButtonStatus> buttonStatusInput = new Input<>("buttonStatus", "whether to show add and delete buttons. " +
"This can be " + Arrays.toString(InputEditor.ButtonStatus.values()) + " (default 'ALL')", InputEditor.ButtonStatus.ALL, InputEditor.ButtonStatus.values());
String[] pathComponents;
String[] conditionalAttribute;
String[] conditionalValue;
Class<?> type;
/**
* beastObjects associated with inputs *
*/
List<BEASTInterface> inputs;
/**
* beastObjects associated with inputs before editing starts *
*/
List<BEASTInterface> startInputs;
/**
* beastObjects that are parents, i.e. contain inputs of m_inputs *
*/
List<BEASTInterface> parentBEASTObjects;
List<Input<?>> parentInputs = new ArrayList<>();
/**
* flag to indicate we are dealing with a list input *
*/
boolean isList;
FlexibleInput<?> _input;
class FlexibleInput<T> extends Input<T> {
FlexibleInput() {
// sets name to something non-trivial This is used by canSetValue()
super("xx", "");
}
public FlexibleInput(T arrayList) {
super("xx", "", arrayList);
}
@Override
public void setType(Class<?> type) {
theClass = type;
}
}
@Override
public void initAndValidate() {
pathComponents = pathInput.get().split("/");
if (pathComponents[0].equals("")) {
pathComponents = new String[0];
}
conditionalAttribute = new String[pathComponents.length];
conditionalValue = new String[pathComponents.length];
for (int i = 0; i < pathComponents.length; i++) {
int j = pathComponents[i].indexOf('[');
if (j >= 0) {
String conditionalComponents = pathComponents[i].substring(j + 1, pathComponents[i].lastIndexOf(']'));
String[] strs = conditionalComponents.split("=");
conditionalAttribute[i] = strs[0];
conditionalValue[i] = strs[1].substring(1, strs[1].length() - 1);
pathComponents[i] = pathComponents[i].substring(0, j);
}
}
inputs = new ArrayList<>();
startInputs = new ArrayList<>();
BEASTObjectPanel.getID(this);
}
/**
* more elegant getters for resolving Input values*
*/
public String getName() {
return nameInput.get();
}
public Partition hasPartition() {
return hasPartitionsInput.get();
}
public boolean addButtons() {
return false;
}
public String getIcon() {
return iconInput.get();
}
public String getTipText() {
return tipTextInput.get();
}
public InputEditor.ExpandOption forceExpansion() {
return forceExpansionInput.get();
}
/**
* Find the input associated with this panel
* based on the path Input.
*/
@SuppressWarnings("unchecked")
final public Input<?> resolveInput(BeautiDoc doc, int partitionIndex) {
try {
// if (parentBEASTObjects != null && parentBEASTObjects.size() > 0 && _input != null)
// System.err.println("sync " + parentBEASTObjects.get(partitionIndex) + "[?] = " + _input.get());
List<BEASTInterface> beastObjects;
if (hasPartitionsInput.get() == Partition.none) {
beastObjects = new ArrayList<>();
beastObjects.add(doc.mcmc.get());
} else {
beastObjects = doc.getPartitions(hasPartitionsInput.get().toString());
}
parentBEASTObjects = new ArrayList<>();
parentInputs = new ArrayList<>();
parentBEASTObjects.add(doc);
parentInputs.add(doc.mcmc);
type = doc.mcmc.getType();
isList = false;
for (int i = 0; i < pathComponents.length; i++) {
List<BEASTInterface> oldPlugins = beastObjects;
beastObjects = new ArrayList<>();
parentBEASTObjects = new ArrayList<>();
parentInputs = new ArrayList<>();
for (BEASTInterface beastObject : oldPlugins) {
final Input<?> namedInput = beastObject.getInput(pathComponents[i]);
type = namedInput.getType();
if (namedInput.get() instanceof List<?>) {
isList = true;
List<?> list = (List<?>) namedInput.get();
if (conditionalAttribute[i] == null) {
for (Object o : list) {
BEASTInterface beastObject2 = (BEASTInterface) o;
beastObjects.add(beastObject2);
parentBEASTObjects.add(beastObject);
parentInputs.add(namedInput);
}
//throw new Exception ("Don't know which element to pick from the list. List component should come with a condition. " + m_sPathComponents[i]);
} else {
int matches = 0;
for (int j = 0; j < list.size(); j++) {
BEASTInterface beastObject2 = (BEASTInterface) list.get(j);
if (matches(beastObject2, conditionalAttribute[i], conditionalValue[i])) {
beastObjects.add(beastObject2);
parentBEASTObjects.add(beastObject);
parentInputs.add(namedInput);
matches++;
break;
}
}
if (matches == 0) {
parentInputs.add(namedInput);
parentBEASTObjects.add(beastObject);
}
}
} else if (namedInput.get() instanceof BEASTInterface) {
isList = false;
if (conditionalAttribute[i] == null) {
beastObjects.add((BEASTInterface) namedInput.get());
parentBEASTObjects.add(beastObject);
parentInputs.add(namedInput);
} else {
if (matches(beastObject, conditionalAttribute[i], conditionalValue[i])) {
// if ((m_sConditionalAttribute[i].equals("id") && beastObject.getID().equals(m_sConditionalValue[i])) ||
// (m_sConditionalAttribute[i].equals("type") && beastObject.getClass().getName().equals(m_sConditionalValue[i]))) {
beastObjects.add(beastObject);
parentBEASTObjects.add(beastObject);
parentInputs.add(namedInput);
}
}
} else {
throw new IllegalArgumentException("input " + pathComponents[i] + " is not a beastObject or list");
}
}
}
if (typeInput.get() != null) {
type = Class.forName(typeInput.get());
}
// sanity check
if (!isList && (hasPartitionsInput.get() == Partition.none) && beastObjects.size() > 1) {
Log.warning.println("WARNING: multiple beastObjects match, but hasPartitions=none");
// this makes sure that all mathing beastObjects are available in one go
isList = true;
// this suppresses syncing
parentInputs.clear();
}
inputs.clear();
startInputs.clear();
for (BEASTInterface beastObject : beastObjects) {
inputs.add(beastObject);
startInputs.add(beastObject);
}
if (!isList) {
_input = new FlexibleInput<>();
} else {
_input = new FlexibleInput<>(new ArrayList<>());
}
_input.setRule(Validate.REQUIRED);
syncTo(partitionIndex);
// if (parentBEASTObjects != null && parentBEASTObjects.size() > 0)
// System.err.println("sync " + parentBEASTObjects.get(partitionIndex) + "[?] = " + _input.get());
if (isList) {
checkForDups((List<Object>) _input.get());
}
return _input;
} catch (Exception e) {
Log.err.println("Warning: could not find objects in path " + Arrays.toString(pathComponents));
}
return null;
} // resolveInputs
private void checkForDups(List<Object> list) {
for (int i = 0; i < list.size(); i++) {
if (list.lastIndexOf(list.get(i)) != i) {
Log.warning.println("We have a dup: " + list.get(i));
list.remove(i);
i--;
}
}
}
private boolean matches(BEASTInterface beastObject2, String conditionalAttribute, String conditionalValue) {
if (conditionalAttribute.equals("id") && beastObject2.getID().equals(conditionalValue)) return true;
if (conditionalAttribute.equals("type") && beastObject2.getClass().getName().equals(conditionalValue)) return true;
if (conditionalAttribute.equals("type!") && !beastObject2.getClass().getName().equals(conditionalValue))
return true;
return false;
}
@SuppressWarnings("unchecked")
public void sync(int partitionIndex) {
if (parentInputs.size() > 0 && _input.get() != null) {
final Input<?> input = parentInputs.get(partitionIndex);
if (isList) {
List<Object> list = (List<Object>) _input.get();
List<Object> targetList = ((List<Object>) input.get());
//targetList.clear();
// only clear former members
for (BEASTInterface beastObject : startInputs) {
targetList.remove(beastObject);
}
targetList.addAll(list);
// sync outputs of items in list
for (Object o : list) {
if (o instanceof BEASTInterface) {
((BEASTInterface)o).getOutputs().add(parentBEASTObjects.get(partitionIndex));
}
}
} else {
try {
//System.err.println("sync " + parentBEASTObjects.get(partitionIndex) + "[" + input.getName() + "] = " + _input.get());
input.setValue(_input.get(), parentBEASTObjects.get(partitionIndex));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* initialise m_input, and either m_beastObject or m_pluginList *
*/
public void syncTo(int partitionIndex) {
_input.setType(type);
try {
if (isList) {
for (BEASTInterface beastObject : inputs) {
_input.setValue(beastObject, this);
}
} else {
if (inputs.size() > 0) {
BEASTInterface beastObject = inputs.get(partitionIndex);
_input.setValue(beastObject, this);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
final Input<?> getInput() {
return _input;
}
/**
* returns name of the beastObject *
*/
public String getType() {
if (isList) {
return inputs.get(0).getClass().getName();
}
if (_input == null) {
return null;
}
return _input.get().getClass().getName();
}
// public void addItem(BEASTObject beastObject) {
// m_parentInputs.add(m_parentInputs.get(m_parentInputs.size()-1));
// m_parentPlugins.add(m_parentPlugins.get(m_parentPlugins.size()-1));
// }
}