package au.com.acpfg.misc.jemboss.settings; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.swing.JComponent; import org.knime.core.data.DataCell; import org.knime.core.data.DataTableSpec; import org.knime.core.node.InvalidSettingsException; import au.com.acpfg.misc.jemboss.io.UnmarshallerInterface; import au.com.acpfg.misc.jemboss.local.AbstractTableMapper; import au.com.acpfg.misc.jemboss.local.ProgramSettingsListener; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException; /** * Simple implementation of a single program setting: name=value with slightly friendly improvements * (eg. default values etc.) * * @author andrew.cassin * */ public abstract class ProgramSetting implements UnmarshallerInterface { // DATA MEMBERS private final HashMap<String,String> m_attrs; // Handlers for the data formats produced by various emboss programs private final static Map<String, UnmarshallerInterface> m_um = new HashMap<String, UnmarshallerInterface>(); protected ProgramSetting(HashMap<String,String> attrs) { assert(attrs != null && attrs.containsKey("type") && attrs.containsKey("name") && attrs.containsKey("default_value")); m_attrs = attrs; } /** * Returns the EMBOSS ACD type for the setting eg. integer, string, etc. * @return may be <code>null</code> if something goes wrong with parsing an EMBOSS .acd file */ public String getType() { return m_attrs.get("type"); } /** * returns the name for the EMBOSS ACD setting * @return may be <code>null</code> if something goes wrong with parsing an EMBOSS .acd file */ public String getName() { return m_attrs.get("name"); } /** * Returns <code>true</code> if the specified key is present in the given settings attributes, * <code>false</code> otherwise. Only intended to be accessible to settings-derived classes. * * @param key standardised attribute name to test (always lowercase) * @return */ protected boolean hasAttribute(String key) { return (m_attrs != null && key != null && m_attrs.containsKey(key)); } public String getAttributeValue(String key) { if (!hasAttribute(key)) return null; return m_attrs.get(key); } /** * If no default value is present, returns an empty string but must not return <code>null</code> * @return */ public String getDefaultValue() { String dv = m_attrs.get("default-value"); if (dv == null) return ""; return dv; } /** * Returns true if the setting is to come from a table column (input setting), false otherwise * @return */ public boolean isInputFromColumn() { return (getColumnName() != null); } /** * Returns the name of the KNIME table column which the setting comes from. If the specified * setting is not a column-related setting, <code>null</code> is returned * @param dt * @return */ public abstract String getColumnName(); /** * Subclasses will want to override this to provide a more sensible implementation * * @param dt * @return */ public abstract JComponent make_widget(DataTableSpec dt); @Override public String toString() { HashMap<String,String> a = new HashMap<String,String>(); copy_attributes(a); StringBuffer sb = new StringBuffer(10*1024); for (String k : a.keySet()) { sb.append(k); sb.append('='); sb.append(a.get(k)); sb.append('\n'); } return Base64.encode(sb.toString().getBytes()); } protected final HashMap<String,String> getAttributes() { return m_attrs; } /** * Subclasses must override these this methods to ensure their class is correctly persisted */ public void copy_attributes(HashMap<String,String> attrs) { for (String k : m_attrs.keySet()) { attrs.put(k, m_attrs.get(k)); } } /** * Human-readable description of a program setting * @param descr */ public void setDescription(String descr) { m_attrs.put("description", descr); } public String getPrettyDescription() { String descr = m_attrs.get("description"); if (descr == null || descr.length() < 1) { return "No help available."; } else if (descr.length() > 60) { return ""+descr.substring(0, 60)+"..."; } else { return descr; } } /** * Returns the value of the setting (after the option) * @return * @throws Exception */ public abstract void getArguments(ProgramSettingsListener l) throws Exception; /** * Pushes the content of DataCell c into the specified file for the specified program setting * (ie. taking the type of setting into account). Any existing file is emptied before the marshalling * process begins. * * @param c * @param file * @throws IOException */ public abstract void marshal(String id, DataCell c, PrintWriter fw) throws IOException, InvalidSettingsException; /** * If <code>b</code> is true the setting will appear on the input settings tabbed pane. Only one of the input/output/optional * may be set at any one time. * @param b */ public void setIsInput(boolean b) { m_attrs.put("is-input", new Boolean(b).toString()); } public void setIsOutput(boolean b) { m_attrs.put("is-output", new Boolean(b).toString()); } public void setIsOptional(boolean b) { m_attrs.put("is-optional", new Boolean(b).toString()); } public boolean isInput() { return (m_attrs.containsKey("is-input") && new Boolean(m_attrs.get("is-input")).booleanValue()); } public boolean isOutput() { return (m_attrs.containsKey("is-output") && new Boolean(m_attrs.get("is-output")).booleanValue()); } public boolean isOptional() { return (m_attrs.containsKey("is-optional") && new Boolean(m_attrs.get("is-optional")).booleanValue()); } public boolean isList() { return (this instanceof ListSetting); } /** * Every subclass must implement this method so that they are instantiated for the * given ACD types that are supported by the class. Must return <code>true</code> for an * ACD type that is supported by the class, <code>false</code> otherwise. * * @param acd_type * @return */ public static boolean canEmboss(String acd_type) { return false; } /** * Factory method for instantiating the correct setting from the field=value pairs in the map... */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static ProgramSetting make(HashMap<String,String> attrs) throws InvalidSettingsException { String t = attrs.get("type").trim().toLowerCase(); Class[] classes = new Class[] { SequenceSetting.class, GraphSetting.class, BooleanSetting.class, CodonUsageTableSetting.class, DataFileSetting.class, ListSetting.class, MatrixSetting.class, NumberSetting.class, OutputFileSetting.class, RangeSetting.class, RegexpSetting.class, StringSetting.class, SequenceSetSetting.class, AlignmentSetting.class, ArraySetting.class // must always be at the end of the list (always accepts any ACD type) ,DummySetting.class }; for (Class c : classes) { try { Method m = c.getMethod("canEmboss", String.class); Boolean ok = (Boolean) m.invoke(null,t); if (ok.booleanValue()) { Constructor cons = c.getDeclaredConstructor(HashMap.class); return (ProgramSetting) cons.newInstance(attrs); } } catch (NoSuchMethodException e) { e.printStackTrace(); continue; // try next class } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return null; } public static ProgramSetting make(String ser_form) throws InvalidSettingsException, Base64DecodingException { HashMap<String,String> attrs = new HashMap<String,String>(); String[] attr_lines = new String(Base64.decode(ser_form)).split("\n"); for (String l : attr_lines) { int first_equal_idx = l.indexOf('='); if (first_equal_idx < 0) throw new InvalidSettingsException("Programmer Error: field=value expected!"); String field = l.substring(0, first_equal_idx); String value = l.substring(first_equal_idx+1); attrs.put(field, value); } return make(attrs); } public boolean isNumber() { return (this instanceof NumberSetting); } public void setMinMax(String min_bound, String max_bound) { // NO-OP: only NumberSetting overrides this method } public void unmarshal(File f, AbstractTableMapper atm, String emboss_prog) throws InvalidSettingsException,IOException { String[] tries = new String[] { emboss_prog + ":" + getName(), emboss_prog + ":" + getType(), getName(), getType() }; // try to find the best unmarshaller (in the above order) for the specified setting UnmarshallerInterface ui = null; for (String t : tries) { ui = m_um.get(t); if (ui != null) { FileInputStream fis = new FileInputStream(f); ui.process(this, fis, atm); fis.close(); return; } } } /****************** FORMATTEDUNMARSHALLERINTERFACE METHODS ***********************/ /** * Returns the type of cell which supports the settings value (output settings only) * Most will be a <code>StringCell.TYPE</code> but we dont provide a default to force * subclasses to implement * * @return */ public void addColumns(AbstractTableMapper atm, ProgramSetting ps) { UnmarshallerInterface ui = m_um.get(ps.getName()); if (ui != null) { ui.addColumns(atm, ps); } } /** * Used to add cells to both output ports for the node (raw and formatted). See * <code>UnmarshallerInterface</code> for details */ public void process(ProgramSetting ps, InputStream out_file, AbstractTableMapper atm) throws IOException,InvalidSettingsException { } public static void addUnmarshaller(String acd_type, UnmarshallerInterface um) { m_um.put(acd_type, um); } public static void addUnmarshaller(String[] acd_types, UnmarshallerInterface um) { for (String acd_type : acd_types) { addUnmarshaller(acd_type, um); } } }