Plugins are generally used to add dynamically loaded modules to an application based on user defined configuration.
Our chemical calculator plugin structure is added to Marvin for the following reasons:
We developed a mechanism to handle these calculations in a uniform way. This common interface is utilized as a common java API for developers, as a command line tool and also in our graphical applications and applets MarvinSketch and MarvinView.
The implementation of the general plugin handling mechanism can be found in the
chemaxon.marvin.plugin
package. Our specific plugin implementations
are in the chemaxon.marvin.calculations
package.
Calculator plugins have a common base class:
chemaxon.marvin.plugin.CalculatorPlugin
.
This base class implements the license handling, provides some helper functions for number
formatting and declares methods to be implemented by the specific plugin classes for
parameter setting, input molecule setting, performing the calculation, and getting the results.
Apart from this main plugin class, our graphical applications and applets
MarvinSketch and MarvinView require a
chemaxon.marvin.plugin.CalculatorPluginDisplay
class to provide result GUI components.
For most plugins, the default implementation is sufficient as long as the
CalculatorPlugin.getResultMolecule()
method is implemented such that molecular results
are written to molecule properties by
Molecule.setProperty(String key, String value)
and atomic properties are written to
extra lables by
MolAtom.setExtraLabel(String label)
. It is also possible to set the atom label display color by
MolAtom.setExtraLabelColor(long color)
.
The CalculatorPluginDisplay
base class provides a MarvinView display for all of these in
CalculatorPluginDisplay.getResultComponent()
.
However, your plugin may require a different display, in which case you should write a custom display class
extending
chemaxon.marvin.plugin.CalculatorPluginDisplay
which overrides
CalculatorPluginDisplay.getResultComponent()
. You can also rewrite
CalculatorPluginDisplay.store()
to store results - this is mainly useful when more molecules
are drawn as a multi-fragment molecule in the sketcher and your plugin handles these one-by-one
(
CalculatorPlugin.handlesMultiFragmentMolecules()
returns false
).
There is an example plugin implementation with a test application among the Marvin Beans Examples.
In Marvin applications and applets plugin parameters can be set in a plugin specific parameter panel.
This can be configured in a corresponding XML in the xjars
directory.
If the parameter
setting panel is more complicated then it is also possible to return it in
CalculatorPluginDisplay.getParameterPanel()
.
The cxcalc command line tool uses
chemaxon.marvin.plugin.CalculatorPluginOutput
to generate the plugin results in table form.
This class implements the default table output with one result row for each input molecule,
the molecule ID in the first column followed by the plugin results in the subsequent
columns. A specific output table format can be defined by subclassing this class.
Both the cxcalc command line tool and the
graphical applications and applets require separate configuration files that specify the
available plugins. For the configuration of the command line tool see the
Configuration File section in the
Calculator user manual. The plugin configuration for
MarvinSketch and MarvinView
is specified in one or more java properties file given in the toolfiles
application or applet parameter. If more than one file is specified, then the file names should be
separated by a ',' character (without spaces) and their contents will be merged.
The file names should be given relative to the CLASSPATH
. Marvin applets
load configuration files from the server computer. If no configuration file is given
then the default configuration file is read:
xjars/plugins.properties
,
taken relative to the Marivn root directory.
The configuration file syntax is best shown by an example:
#$<plugin class name>$<plugin JAR URL>$<menu>$<mnemonics>$<group>$<groupmnemonics>$<NOPARAMS>$<PRELOAD> #first char is separator, you can choose any ASCII character that is not contained in the fields plugin_01=$chemaxon.marvin.calculations.ElementalAnalyserPlugin$ElementalAnalyserPlugin.jar$Elemental Analysis$EA$$ plugin_11=$chemaxon.marvin.calculations.pKaPlugin$pKaPlugin.jar$pKa$pK$Protonation$P
The property keys should be unique within one configuration file.
The Tools
menu is constructed with submenus specified in group strings with menuitems defined in menu strings.
Mnemonics can be set for groups using groupmnemonics and for plugins defining mnemonics strings. If group string is
left empty then the plugin is listed directly in the Tools
menu.
Each property value defines a plugin configuration by giving the following fields:
xjars
directory)
Tools
menu
Tools
menu
At least one of the plugin class and the plugin JAR is mandatory. If the plugin class is not
specified then it is read from the Plugin-Class
attribute of the JAR manifest. If the
JAR is not specified then the plugin is loaded from the CLASSPATH.
The ending "NOPARAMS" or "PRELOAD" options can simply be omitted, while if an intermediate field
is omitted then the corresponding separator characted should be added.
Plugin loading:
The central plugin loader class is the
chemaxon.marvin.plugin.PluginFactory
:
this class reads the configuration from a java.util.Properties
object or from
a configuration file. In the latter case the Tools
menu is also constructed
based on this configuration. The parameter panels are dynamically constructed from the XMLs
found in the xjars
directory or fetched from the display class. In the former case
the paremter panel can be constructed and displayed before the plugin class is loaded.
The plugin loading mechanism is the following: first the program tries to load the plugin class by the default class loader from the CLASSPATH; if this the plugin class is not found then the JAR is loaded and the system tries to load the plugin class from there.
If the plugin name is omitted then the plugin is loaded directly from the JAR where
the Plugin-Class
manifest attribute specifies the plugin class.
If the JAR name is omitted then the plugin is loaded from the CLASSPATH.
For example, here is the above plugin configuration with omitted fields:
#$<plugin class name>$<plugin JAR URL>$<menu>$<mnemonics>$<group>$<groupmnemonics>$<NOPARAMS>$<PRELOAD> #first char is separator, you can choose any ASCII character that is not contained in the fields plugin_01=$chemaxon.marvin.calculations.ElementalAnalyserPlugin$$Elemental Analysis$EA plugin_11=$$pKaPlugin.jar$pKa$pK$Protonation$P
The following table shows the items needed for the different plugin uses:
CalculatorPlugin subclass | CalculatorPluginDisplay class or subclass | CalculatorPluginOutput class or subclass | plugins.properties | calc.properties | |
sketcher/viewer | |||||
cxcalc tool | |||||
java API |
Naming convention:
The plugin handling mechanism currently requires the following naming convention:
...Plugin
(e.g. chemaxon.marvin.calculations.HBDAPlugin
)
<plugin class name>Display
(e.g. chemaxon.marvin.calculations.HBDAPluginDisplay
)
<plugin class name>Output
(e.g. chemaxon.marvin.calculations.HBDAPluginOutput
)
There is an example plugin implementation (plugin and display classes) with a test application among the Marvin Beans Examples.
Here are some guidelines to follow when designing your own plugin, as a
CalculatorPlugin
subclass
The first step: go to the internet and download some code or write your own. The calculation code is assumed to work on one input molecule at a time, perform the calculation and then return various results of the calculation. The plugin class will first set the input molecule, then run the calculation and finally query the results, so it is a good idea to follow roughly the same implementation style in the calculation module: the more the calculation code follows this model, the easier your work will be when you write the plugin wrapper.
Then extend the abstract base plugin class chemaxon.marvin.plugin.CalculatorPlugin
.
Here is the list of methods that have to be implemented:
public boolean validate(String license)
Implement this only if you want to sell your plugin and protect it with a license key.
The default implementation always returns true
which means that the plugin is free:
no license key is required.
A license key is an 8-character string that is hard-coded in your plugin and sold to your clients
in the license key file chemaxon/licenses.dat
located in the
.chemaxon/licenses.dat
(Unix) or
chemaxon/licenses.dat
(Windows) file under the user's home directory.
In the license key file, the plugin name with full package name is paired with the
license key: chemaxon.marvin.calculations.YourPlugin=56TYAD12The implementation of this method is easy: simply check if the argument coincides with the plugin license key:
public final boolean validate(String license) { return "56TYAD12".equals(license); // return your license key here }This method is declared
final
to prevent the intruder from
subclassing your plugin and replacing your license key by overwriting this method.
public void setParameters(Properties params) throws PluginException
This method sets the plugin specific parameters: the params
argument contains the
plugin parameters as long parameter name -> parameter value
pairs.
(The long parameter name
here is without the "--
" prefix: e.g. if you
have --type
as a command line parameter then it will be present with key
type
in this property table.) Your task is to
convert the parameter values from string to the required format and set the parameter
in the calculation module or store it in the plugin for later use. Throw a
PluginException
on any error (unexpected format, unexpected value). All possible
plugin parameters have a default value so a missing parameter should not cause any error: use
its default value instead.
public void checkMolecule(Molecule mol) throws PluginException
Checks the input molecule. Throws a PluginException
if the plugin calculation
result is not defined for the given molecule (e.g. molecule is a reaction molecule or a
molecule with R-groups). The exception message will be formed to an error message to the
user and the molecule will not be processed if a PluginException
is thrown.
Do nothing if the molecule is accepted. The default implementation accepts
all molecules (simply returns).
public void setMolecule(Molecule mol) throws PluginException
This sets the input molecule. Again, throw a PluginException
on any error.
public void run() throws PluginException
This method performs the calculation and stores the results. Include those tasks
that must be run once for each molecule and produce the calculation results in the end.
Throw a PluginException
on any error.
public Molecule getMolecule()
This simply returns the input molecule.
public Object[] getResultTypes()
This method returns the queried result types. For example, the charge
calculation may have three result types: sigma
, pi
and total
,
the logp
calculation may have two result types: increments
and
molecule
. The built-in plugins charge
, logp
and
pka
have the --type
command line parameter that specifies the required
result types: this method returns those that are specified in this parameter. However, it is
possible to return all available result types and not provide this choice.
public int getResultDomain(Object type)
This returns the domain that the calculation result for the specified result type refers to:
currently it can be ATOM
or MOLECULE
. For example, the
logPPlugin
returns ATOM
if key
is "increments"
and returns MOLECULE
if key
is "molecule"
.
public int getResultCount(Object type)
This returns the number of result items for the specified result type. For ATOM
result
domain this is usually the number of atoms in the molecule, for MOLECULE
domain this
is usually 1
.
public Object getResult(Object type, int index) throws PluginException
This returns the result for the specified result type and the specified result index: this index
must be at least 0
and less than the result count returned by
getResultCount(Object type)
for this result type. In our case the result is a number:
it must be wrapped into the derived class of java.lang.Number
corresponding to its
primitive type (e.g. double
must be wrapped into java.lang.Double
).
PluginException
can be thrown on any error.
public String getResultAsString(Object type, int index, Object result) throws PluginException
This returns a string representation of the result. The result type and index are also given:
in some cases the string representation may include these or depend on these as well. The
protected String format(double x)
can be used to double -> String
conversion with a given number of fractional digits. If you intend to use this formatting then
protected void setDoublePrecision(int precision)
has to be called once beforehand
to set the maximum number of decimal digits allowed in the fractional portion of a number.
PluginException
can be thrown on any error.
public String getResultAsRGB(Object type, int index, Object result) throws PluginException
Returns the color to be used when displaying the result. For example, this method is used when acidic pKa values are displayed
in red while basic pKa values are displayed in blue. The color is returned as a single int
(see the
java.awt.Color API for a
definition of encoding a color into a single int
). The default implementation returns 0
which means that the result will be displayed using the current foreground color.
PluginException
can be thrown on any error.
protected void standardize(Molecule mol)
This method is used to bring the molecule to a standardized form. Some calculations require a certain
standardization preprocess, such as aromatization or a common form of certain functional groups
(one prescribed tautomer or mezomer form). The current implementation performs only aromatization and
nitro group and sulphynil group conversions (transforms to the neutral forms). If any other transformation is
needed or no such transformation is necessary then you must implement this method. Be careful with
transformations that change the atom set of the molecule since these change the atom indices as
well: if the result domain is ATOM
then after querying the results with
getResult(Object key, int index)
and
getResultAsString(Object key, int index, Object result)
the program will output the returned result for the specified atom index
in the original molecule and not in the transformed one.
If the standardization procedure changes the atom indices then the index given in these result
query methods must be transformed to the corresponding atom index in the transformed molecule
and the result for that atom index must be returned.
Here are some examples that illustrate the usage of the plugin API.
Note the validate(<license key>)
is called right after the plugin
instantiation: this is important because otherwise you can only use the plugin
without license in demo mode. There are some plugins that do not require a license key
(ElementalAnalyserPlugin and MajorMicrospeciesPlugin). API usage examples can be found
for each plugin in the API header of the plugin class: