LPhy Extension Mechanism
The LPhy extension mechanism is implemented using the Service Provider Interface (SPI)
and the Java Platform Module System (JPMS).
An LPhy extension has to be a Java module, which also needs to implement the interface
LPhyExtension.java
to provide a list of the required classes to extend the functionality of LPhy.
Project structure
The LPhy repository contains two modules
separated in two subfolder, where lphy
contains core classes, such as parser, distributions, functions and data types,
and lphy-studio
are GUI. The project folder structure looks like:
linguaPhylo
├── build.gradle.kts
├── examples
├── lphy
│ ├── build.gradle.kts
│ ├── doc
│ └── src
│ ├── main
│ │ ├── java
│ │ │ ├── lphy
│ │ │ └── module-info.java
│ │ └── resources
│ └── test
│
├──lphy-studio
│ ├── build.gradle.kts
│ └── src
│ ├── main
│ │ ├── java
│ │ │ ├── lphystudio
│ │ │ └── module-info.java
│ │ └── resources
│ └── test
├──settings.gradle.kts
└──tutorials
SPI and the extension’s container provider class
The module lphy
declares the package dependencies and SPI LPhyExtension
using the following code.
Because we consider the core itself as an extension, the Container Provider class LPhyExtImpl
is also required to declare in this module using the following one-line code.
exports lphy.spi;
uses lphy.spi.LPhyExtension;
provides lphy.spi.LPhyExtension with lphy.spi.LPhyExtImpl;
Here is the source code of SPI in LPhy :
package lphy.spi;
import ...;
/**
* The service interface defined for SPI.
* Implement this interface to create one "Container" provider class
* for each module of LPhy or its extensions,
* which should include {@link GenerativeDistribution}, {@link Func},
* and {@link SequenceType}.
*
* @author Walter Xie
*/
public interface LPhyExtension {
/**
* @return the list of new {@link GenerativeDistribution} implemented
* in the LPhy extension.
*/
List<Class<? extends GenerativeDistribution>> getDistributions();
/**
* @return the list of new {@link Func} implemented in the LPhy extension.
*/
List<Class<? extends Func>> getFunctions();
/**
* @return the map of new {@link SequenceType} implemented in the LPhy extension.
* The string key is a keyword to represent this SequenceType.
* The keyword can be used to identify and initialise the
* corresponding sequence type.
*/
Map<String, ? extends SequenceType> getSequenceTypes();
}
Then the Container Provider class LPhyExtImpl
lists all classes required to be extended:
GenerativeDistribution, Func, and SequenceType.
package lphy.spi;
import ...;
/**
* The "Container" provider class that implements SPI
* which include a list of {@link GenerativeDistribution}, {@link Func},
* and {@link SequenceType} to extend.
* It requires a public no-args constructor.
* @author Walter Xie
*/
public class LPhyExtImpl implements LPhyExtension {
List<Class<? extends GenerativeDistribution>> genDists = Arrays.asList(
// probability distribution
Normal.class, LogNormal.class, Exp.class,
... );
List<Class<? extends Func>> functions = Arrays.asList(ARange.class, ArgI.class,
// Substitution models
JukesCantor.class, K80.class, F81.class, HKY.class,
... );
/**
* Required by ServiceLoader.
*/
public LPhyExtImpl() { }
@Override
public List<Class<? extends GenerativeDistribution>> getDistributions() {
return genDists;
}
@Override
public List<Class<? extends Func>> getFunctions() {
return functions;
}
@Override
public Map<String, ? extends SequenceType> getSequenceTypes() {
Map<String, SequenceType> dataTypeMap = new ConcurrentHashMap<>();
dataTypeMap.put("rna", SequenceType.NUCLEOTIDE);
dataTypeMap.put("dna", SequenceType.NUCLEOTIDE);
...
return dataTypeMap;
}
}
These two classes can be seen from the package lphy.spi.
In the next sub-project lphy-studio
, even though it is not going to extend anything from lphy
,
we still create an empty class LPhyStudioImpl as a flag to make it recognised by the extension mechanism.
Setting up
The project is migrated to a Gradle project from version 1.1.0. How to set up the project is available in the developer note.