package beast.app.draw;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import beast.app.beauti.BeautiConfig;
import beast.app.beauti.BeautiDoc;
import beast.app.beauti.BeautiSubTemplate;
import beast.app.draw.InputEditor.ButtonStatus;
import beast.app.draw.InputEditor.ExpandOption;
import beast.core.BEASTInterface;
import beast.core.Input;
import beast.core.Input.Validate;
import beast.core.util.Log;
import beast.util.AddOnManager;
/** Can create InputEditors for inputs of BEASTObjects
* and there are some associated utility methods **/
public class InputEditorFactory {
/**
* map that identifies the InputEditor to use for a particular type of Input *
*/
HashMap<Class<?>, String> inputEditorMap;
HashMap<Class<?>, String> listInputEditorMap;
BeautiDoc doc;
public InputEditorFactory(BeautiDoc doc) {
this.doc = doc;
init();
}
public void init() {
// register input editors
inputEditorMap = new HashMap<>();
listInputEditorMap = new HashMap<>();
// String [] knownEditors = new String [] {"beast.app.draw.DataInputEditor","beast.app.beauti.AlignmentListInputEditor", "beast.app.beauti.FrequenciesInputEditor", "beast.app.beauti.OperatorListInputEditor", "beast.app.beauti.ParametricDistributionInputEditor", "beast.app.beauti.PriorListInputEditor", "beast.app.beauti.SiteModelInputEditor", "beast.app.beauti.TaxonSetInputEditor", "beast.app.beauti.TipDatesInputEditor", "beast.app.draw.BooleanInputEditor", "beast.app.draw.DoubleInputEditor", "beast.app.draw.EnumInputEditor", "beast.app.draw.IntegerInputEditor", "beast.app.draw.ListInputEditor",
// "beast.app.draw.ParameterInputEditor", "beast.app.draw.PluginInputEditor", "beast.app.draw.StringInputEditor"};
// registerInputEditors(knownEditors);
String[] PACKAGE_DIRS = {"beast.app",};
for (String packageName : PACKAGE_DIRS) {
List<String> inputEditors = AddOnManager.find("beast.app.draw.InputEditor", packageName);
registerInputEditors(inputEditors.toArray(new String[0]));
}
}
private void registerInputEditors(String[] inputEditors) {
//BeautiDoc doc = new BeautiDoc();
for (String inputEditor : inputEditors) {
// ignore inner classes, which are marked with $
if (!inputEditor.contains("$")) {
try {
Class<?> _class = Class.forName(inputEditor);
Constructor<?> con = _class.getConstructor(BeautiDoc.class);
InputEditor editor = (InputEditor) con.newInstance(doc);
//InputEditor editor = (InputEditor) _class.newInstance();
Class<?>[] types = editor.types();
for (Class<?> type : types) {
inputEditorMap.put(type, inputEditor);
if (editor instanceof ListInputEditor) {
Class<?> baseType = ((ListInputEditor) editor).baseType();
listInputEditorMap.put(baseType, inputEditor);
}
}
} catch (java.lang.InstantiationException e) {
// ingore input editors that are inner classes
} catch (Exception e) {
// print message
Log.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}
}
}
/**
* add all inputs of a beastObject to a box *
*/
public List<InputEditor> addInputs(Box box, BEASTInterface beastObject, InputEditor editor, InputEditor validateListener, BeautiDoc doc) {
/* add individual inputs **/
List<Input<?>> inputs = null;
List<InputEditor> editors = new ArrayList<>();
try {
inputs = beastObject.listInputs();
} catch (Exception e) {
// TODO: handle exception
}
for (Input<?> input : inputs) {
try {
String fullInputName = beastObject.getClass().getName() + "." + input.getName();
if (!doc.beautiConfig.suppressBEASTObjects.contains(fullInputName)) {
InputEditor inputEditor = createInputEditor(input, beastObject, true, ExpandOption.FALSE, ButtonStatus.ALL, editor, doc);
box.add(inputEditor.getComponent());
box.add(Box.createVerticalStrut(5));
//box.add(Box.createVerticalGlue());
if (validateListener != null) {
inputEditor.addValidationListener(validateListener);
}
editors.add(inputEditor);
}
} catch (Exception e) {
// ignore
Log.err.println(e.getClass().getName() + ": " + e.getMessage() + "\n" +
"input " + input.getName() + " could not be added.");
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Could not add entry for " + input.getName());
}
}
box.add(Box.createVerticalGlue());
return editors;
} // addInputs
public InputEditor createInputEditor(Input<?> input, BEASTInterface beastObject, BeautiDoc doc) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return createInputEditor(input, beastObject, true, InputEditor.ExpandOption.FALSE, ButtonStatus.ALL, null, doc);
}
public InputEditor createInputEditor(Input<?> input, BEASTInterface beastObject, boolean addButtons,
ExpandOption forceExpansion, ButtonStatus buttonStatus,
InputEditor editor, BeautiDoc doc) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return createInputEditor(input, -1, beastObject, addButtons, forceExpansion, buttonStatus, editor, doc);
}
public InputEditor createInputEditor(Input<?> input, int listItemNr, BEASTInterface beastObject, boolean addButtons,
ExpandOption forceExpansion, ButtonStatus buttonStatus,
InputEditor editor, BeautiDoc doc) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (input.getType() == null) {
input.determineClass(beastObject);
}
//Class<?> inputClass = input.get() != null ? input.get().getClass(): input.getType();
Class<?> inputClass = input.getType();
if (inputClass == null) {
return null;
}
if (listItemNr >= 0) {
inputClass = ((List<?>)input.get()).get(listItemNr).getClass();
} else {
if (input.get() != null && !input.get().getClass().equals(inputClass)
&& !(input.get() instanceof ArrayList)) {
Log.trace.println(input.get().getClass() + " != " + inputClass);
inputClass = input.get().getClass();
}
}
//Log.trace.print(inputClass.getName() + " => ");
InputEditor inputEditor;
// check whether the super.editor has a custom method for creating an Editor
if (editor != null) {
try {
String name = input.getName();
name = new String(name.charAt(0) + "").toUpperCase() + name.substring(1);
name = "create" + name + "Editor";
Class<?> _class = editor.getClass();
Method method = _class.getMethod(name);
inputEditor = (InputEditor) method.invoke(editor);
//Log.trace.println(inputEditor.getClass().getName() + " (CUSTOM EDITOR)");
return inputEditor;
} catch (Exception e) {
// ignore
}
}
if (listItemNr < 0 && (List.class.isAssignableFrom(inputClass) ||
(input.get() != null && input.get() instanceof List<?>))) {
// handle list inputs
if (listInputEditorMap.containsKey(inputClass)) {
// use custom list input editor
String inputEditorName = listInputEditorMap.get(inputClass);
Constructor<?> con = Class.forName(inputEditorName).getConstructor(BeautiDoc.class);
inputEditor = (InputEditor) con.newInstance(doc);
//inputEditor = (InputEditor) Class.forName(inputEditor).newInstance();
} else {
// otherwise, use generic list editor
inputEditor = new ListInputEditor(doc);
}
((ListInputEditor) inputEditor).setButtonStatus(buttonStatus);
} else if (input.possibleValues != null) {
// handle enumeration inputs
inputEditor = new EnumInputEditor(doc);
} else {
Class<?> inputClass2 = inputClass;
while (inputClass2 != null && !inputEditorMap.containsKey(inputClass2)) {
inputClass2 = inputClass2.getSuperclass();
}
if (inputClass2 == null) {
inputEditor = new BEASTObjectInputEditor(doc);
} else {
// handle BEASTObject-input with custom input editors
String inputEditorName = inputEditorMap.get(inputClass2);
Constructor<?> con = Class.forName(inputEditorName).getConstructor(BeautiDoc.class);
inputEditor = (InputEditor) con.newInstance(doc);
}
}
// } else if (inputEditorMap.containsKey(inputClass)) {
// // handle BEASTObject-input with custom input editors
// String inputEditor = inputEditorMap.get(inputClass);
//
// Constructor<?> con = Class.forName(inputEditor).getConstructor(BeautiDoc.class);
// inputEditor = (InputEditor) con.newInstance(doc);
// //inputEditor = (InputEditor) Class.forName(inputEditor).newInstance(doc);
// //} else if (inputClass.isEnum()) {
// // inputEditor = new EnumInputEditor();
// } else {
// // assume it is a general BEASTObject, so create a default BEASTObject input editor
// inputEditor = new PluginInputEditor(doc);
// }
String fullInputName = beastObject.getClass().getName() + "." + input.getName();
//System.err.println(fullInputName);
ExpandOption expandOption = forceExpansion;
if (doc.beautiConfig.inlineBEASTObject.contains(fullInputName) || forceExpansion == ExpandOption.TRUE_START_COLLAPSED) {
expandOption = ExpandOption.TRUE;
// deal with initially collapsed beastObjects
if (doc.beautiConfig.collapsedBEASTObjects.contains(fullInputName) || forceExpansion == ExpandOption.TRUE_START_COLLAPSED) {
if (input.get() != null) {
Object o = input.get();
if (o instanceof ArrayList) {
for (Object o2 : (ArrayList<?>) o) {
if (o2 instanceof BEASTInterface) {
String id = ((BEASTInterface) o2).getID();
if (!ListInputEditor.g_initiallyCollapsedIDs.contains(id)) {
ListInputEditor.g_initiallyCollapsedIDs.add(id);
ListInputEditor.g_collapsedIDs.add(id);
}
}
}
} else if (o instanceof BEASTInterface) {
String id = ((BEASTInterface) o).getID();
if (!ListInputEditor.g_initiallyCollapsedIDs.contains(id)) {
ListInputEditor.g_initiallyCollapsedIDs.add(id);
ListInputEditor.g_collapsedIDs.add(id);
}
}
}
}
}
inputEditor.setDoc(doc);
inputEditor.init(input, beastObject, listItemNr, expandOption, addButtons);
((JComponent) inputEditor).setBorder(BorderFactory.createEmptyBorder());
inputEditor.getComponent().setVisible(true);
//Log.trace.println(inputEditor.getClass().getName());
return inputEditor;
} // createInputEditor
/**
* find beastObjects that could fit the input
* @param input
* @param parent beastObject containing the input
* @param tabuList list of ids that are not allowed
* @param doc
* @return
*/
public List<String> getAvailablePlugins(Input<?> input, BEASTInterface parent, List<String> tabuList, BeautiDoc doc) {
//List<String> beastObjectNames = BeautiConfig.getInputCandidates(parent, input);
List<String> beastObjectNames = new ArrayList<>();
if (beastObjectNames != null) {
return beastObjectNames;
}
/* add ascendants to tabu list */
if (tabuList == null) {
tabuList = new ArrayList<>();
}
if (!doc.isExpertMode()) {
for (BEASTInterface beastObject : BEASTObjectPanel.listAscendants(parent, doc.pluginmap.values())) {
tabuList.add(beastObject.getID());
}
}
//System.err.println(tabuList);
/* collect all beastObjects in the system, that are not in the tabu list*/
beastObjectNames = new ArrayList<>();
for (BEASTInterface beastObject : doc.pluginmap.values()) {
if (input.getType().isAssignableFrom(beastObject.getClass())) {
boolean isTabu = false;
if (tabuList != null) {
for (String tabu : tabuList) {
if (tabu.equals(beastObject.getID())) {
isTabu = true;
}
}
}
if (!isTabu) {
try {
if (input.canSetValue(beastObject, parent)) {
beastObjectNames.add(beastObject.getID());
}
} catch (Exception e) {
// ignore
}
}
}
}
/* add all beastObject-classes of type assignable to the input */
if (doc.isExpertMode()) {
List<String> classes = AddOnManager.find(input.getType(), "beast");
for (String className : classes) {
try {
Object o = Class.forName(className).newInstance();
if (input.canSetValue(o, parent)) {
beastObjectNames.add("new " + className);
}
} catch (Exception e) {
// ignore
}
}
}
return beastObjectNames;
} // getAvailablePlugins
/**
* finds beauti templates that can create subnets that fit an input
* @param input
* @param parent
* @param tabuList
* @param doc
* @return
*/
public List<BeautiSubTemplate> getAvailableTemplates(Input<?> input, BEASTInterface parent, List<String> tabuList, BeautiDoc doc) {
Class<?> type = input.getType();
List<BeautiSubTemplate> candidates = doc.beautiConfig.getInputCandidates(parent, input, type);
if (input.getRule().equals(Validate.OPTIONAL)) {
candidates.add(BeautiConfig.getNullTemplate(doc));
}
return candidates;
}
}