First off if you've just downloaded phenote from sourceforge obo svn, the (latest) source is located in phenote/trunk/src/java. The most important subdirectories under src/java for creating a data adapter are:
For a good example of a data adapter, check out phenote.dataadapter.phenosyntax.PhenoSyntaxFileAdapter
There are two types of Data Adapters used in Phenote. The first is a File Data Adapter which is in charge of loading and saving the annotations created in a Phenote session. The second is a Queryable Data Adapter which is in charge of loading/writing annotations to databases over a network.
The interface that a phenote file data adapter implements is phenote.dataadapter.DataAdapterI (although this will be renamed FileDataAdapterI in the future), and here is what it looks like:
public interface DataAdapterI {
public void load();
public CharacterListI load(File f);
public void commit(CharacterListI charList);
public void commit(CharacterListI charList, File f);
/** Set value to use for loading or writeback, for a file adapter this would be
the file name */
public void setAdapterValue(String adapterValue);
public List<String> getExtensions();
public String getDescription();
}
The most important methods are load and commit. load()/load(File) will be called by phenote to load up a phenote.datamodel.CharacterListI (see below)
The other kind of data adapter is the queryable data adapter. Typically
this is a data adapter hooked into a database. Here is the phenote.dataadapter.QueryableDataadapterI interface:
public interface QueryableDataAdapterI {
/** return true if data adapter can query for the char field */
public boolean isFieldQueryable(String field);
/** Throws exception if query fails, and no data to return */
public CharacterListI query(String field, String query) throws DataAdapterEx;
}
isFieldQueryable returns true for strings of fields that are queryable through this adapter. The paradigm here is that the user queries the adapter with a field name and value. For instance you can query the pub field with pub id value MED:1234, and ideally the adapter would return a CharacterList of all the characters (phenotypic statements) associated with that publication in the connected database. Another common example is querying by genotype or allele, where given a particular genotype, the adapter will return all the phenotypic statements annotated to that genotype.
The Phenote GUI actually queries the QueryableDataAdapter and for every field that is queryable it puts a "Retrieve" button next to the field. A user will then fill in that field (e.g. MED:1234 in pub field) in the Data Entry panel and hit the retrieve button. The GUI will then call the QueryableDataAdapter's query method with the name of the field ("Pub") and the query string ("Med:1234"). The QueryableDataAdapterI should then return a CharacterList using this query, which will then be loaded into Phenote and typically presented to the user in the Annotation Spreadsheet.
A CharacterListI is just a list of phenote.datamodel.Characters. A Character is basically a phenotypic statement (relating an Entity-Quality phenotype to a genotype, publication, etc.). So basically what a data adapter needs to produce is a list of characters.
A Character is just a set of tag-value fields, where the tag is the name of a field (Entity, Quality, Genotype...), and value is the value of the field. The actual names of the fields come from the phenote configuration (see the section on Customizing Phenote for more information). In other words, the phenote datamodel is proscribed by its configuration file. This means that a data adapter needs to be able to either:
If the set of fields in a configuration are completely different than the set of fields that a data adapter is expecting, then the data adapter won't be able to get/load the data it expects. Therefore, in addition to making a data adapter, you need to make sure a configuration fits with it, or make a new configuration.
To set a field in a character, use:
setValue(CharField cf, String valueString) which throws a phenote.datamodel.TermNotFoundException if the valueString is not found in the ontologies associated with the CharField(via configuration) where phenote.datamodel.CharField is an object that represents a field in a character. To get a char field you can call getCharFieldForName(String fieldName) which throws a phenote.datamodel.CharFieldException if you give it a string that is not from the configuration. Ok even better I just combined this into one convenience method:
setValue(String fieldString, String valueString) throws TermNotFoundException, CharFieldException.
For fields with ontologies (with term completion) the valueString has to be the id for the term (not the term name)
So thats basically it for making characters. Some code might look like this:try {
Character c = new Character();
c.setValue("Entity","GO:123");
c.setValue("Quality","PATO:345");
c.setValue("Genotype","somegenotypehere");
....
}
catch (CharFieldException e) {...} // may want to do this per field - error msg?
catch (TermNotFoundException e) {...} // perhaps per field - error message?
and for CharacterLists just add the characters made above to it:
CharacterList cl = new CharacterList();
cl.add(character1);
cl.add(character2);
...
The CharacterList is passed into the commit method. Iterate through the list of CharacterI's. To get at a Character's field data, just call character.getValueString(String fieldString). This throws a CharFieldException if the fieldString doesn't match a field in your configuration. This returns a String which is the value of that field, in the case of fields with ontologies this is a term id (GO:1234). If you would like more info than just the term id from an ontology field, you can call getTerm(String fieldName). This returns an org.geneontology.oboedit.datamodel.OBOClass from the OBO-Edit datamodel (I may eventually wrap this in a phenote object - not sure).
You can also query the OntologyManager for all existing character fields with OntologyManager.inst().getCharFieldList() which returns a List<CharField>. You can then query whether the Character has a value for a char field with character.hasValue(CharField), and can retrieve a phenote.datamodel.CharFieldValue from the character with getValue(CharField). You can then call charFieldValue.getName() to get the free text string or the id of the field. You can also query if its a term with charFieldValue.isTerm() and if so get its OBOClass with getTerm().
And that's about it. As you can see, there's several ways of getting at Phenote data. Here's what some code may look like:
for (CharacterI ch : characterList.getList()) {
try {
String genotype = ch.getValueString("Genotype");
OBOClass entityTerm = ch.getTerm("Entity");
OBOClass valueTerm = ch.getTerm("Value");
// write this data out to data source...
} catch (CharFieldException ex) {
...error processing...
}
This implies that Genotype, Entity, and Value are all in a configuration file, and if not an exception will be thrown.