
org.biopax.paxtools.io.sbgn.L3ToSBGNPDConverter Maven / Gradle / Ivy
package org.biopax.paxtools.io.sbgn;
import org.biopax.paxtools.io.sbgn.idmapping.HGNC;
import org.biopax.paxtools.model.BioPAXElement;
import org.biopax.paxtools.model.BioPAXLevel;
import org.biopax.paxtools.model.Model;
import org.biopax.paxtools.model.level3.*;
import org.sbgn.Language;
import org.sbgn.SbgnUtil;
import org.sbgn.bindings.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.*;
import java.util.Map;
import static org.sbgn.GlyphClazz.*;
import static org.sbgn.ArcClazz.*;
/**
* This class converts BioPAX L3 model into SBGN PD.
* It optionally applies COSE layout.
*
* This version ignores several BioPAX L3 features during conversion:
*
* - Parent-child relationship between physical entities
* - Parent-child relationship between entity references
* - Binding features and covalent binding features of physical entities
*
*
* Also note that:
*
* - Compartment is just a controlled vocabulary in BioPAX, so nesting and neighborhood relations
* between compartments are not handled here.
* - Control structures in BioPAX and in SBGN PD are a little different. We use AND and NOT
* glyphs to approximate controls in BioPAX. However, ANDing everything is not really proper because
* BioPAX do not imply a logical operator between controllers.
*
*
* @author Ozgun Babur
*/
public class L3ToSBGNPDConverter
{
private static final Logger log = LoggerFactory.getLogger(L3ToSBGNPDConverter.class);
/**
* Ubique label.
*/
public static final String IS_UBIQUE = "IS_UBIQUE";
/**
* A matching between physical entities and SBGN classes.
*/
private static Map, String> typeMatchMap;
/**
* For creating SBGN objects.
*/
private static ObjectFactory factory;
//-- Section: Instance variables --------------------------------------------------------------|
/**
* This class is used for detecting ubiques.
*/
protected UbiqueDetector ubiqueDet;
/**
* This class is used for generating short printable strings (text in info boxes) from
* recognized entity features.
*/
protected FeatureDecorator featStrGen;
/**
* Flag to run a layout before writing down the sbgn.
*/
protected boolean doLayout;
/**
* Mapping from SBGN IDs to the IDs of the related objects in BioPAX.
*/
protected Map> sbgn2BPMap;
/**
* Option to flatten nested complexes.
*/
protected boolean flattenComplexContent;
/**
* SBGN process glyph can be used to show reversible reactions. In that case two ports of the
* process will only have product glyphs. However, this creates an incompatibility with BioPAX:
* reversible biochemical reactions can have catalysis with a direction. But if we use a single
* glyph for the process and direct that catalysis to it, then the direction of the catalysis
* will be lost. If we use two process nodes for the reversible reaction (one for left-to-right
* and another for right-to-left), then we can direct the directed catalysis to only the
* relevant process glyph.
*
* Also note that the layout do not support ports. If layout is run, the ports are removed. In
* that case it will be impossible to distinguish left and right components of the reversible
* process glyph because they will all be product edges.
*/
protected boolean useTwoGlyphsForReversibleConversion;
/**
* ID to glyph map.
*/
Map glyphMap;
/**
* ID to Arc map
*/
Map arcMap;
/**
* ID to compartment map.
*/
Map compartmentMap;
/**
* Set of ubiquitous molecules.
*/
Set ubiqueSet;
//-- Section: Public methods ------------------------------------------------------------------|
/**
* Empty constructor.
*/
public L3ToSBGNPDConverter()
{
this(null, null, true);
}
/**
* Constructor with parameters.
* @param ubiqueDet Ubique detector class
* @param featStrGen feature string generator class
* @param doLayout whether we want to perform layout after SBGN creation.
*/
public L3ToSBGNPDConverter(UbiqueDetector ubiqueDet, FeatureDecorator featStrGen,
boolean doLayout)
{
this.ubiqueDet = ubiqueDet;
this.featStrGen = (featStrGen != null) ? featStrGen : new CommonFeatureStringGenerator();
this.doLayout = doLayout;
this.useTwoGlyphsForReversibleConversion = true;
this.sbgn2BPMap = new HashMap>();
this.flattenComplexContent = true;
}
/**
* Getter class for the parameter useTwoGlyphsForReversibleConversion.
* @return whether use two glyphs for the reversible conversion
*/
public boolean isUseTwoGlyphsForReversibleConversion()
{
return useTwoGlyphsForReversibleConversion;
}
/**
* Sets the option to use two glyphs for the reversible conversion.
* @param useTwoGlyphsForReversibleConversion give true if use two glyphs
*/
public void setUseTwoGlyphsForReversibleConversion(boolean useTwoGlyphsForReversibleConversion)
{
this.useTwoGlyphsForReversibleConversion = useTwoGlyphsForReversibleConversion;
}
public boolean isFlattenComplexContent()
{
return flattenComplexContent;
}
public void setFlattenComplexContent(boolean flattenComplexContent)
{
this.flattenComplexContent = flattenComplexContent;
}
/**
* Converts the given model to SBGN, and writes in the specified file.
*
* @param model model to convert
* @param file file to write
*/
public void writeSBGN(Model model, String file)
{
// Create the model
Sbgn sbgn = createSBGN(model);
// Write in file
try {
SbgnUtil.writeToFile(sbgn, new File(file));
}
catch (JAXBException e) {
throw new RuntimeException("writeSBGN, SbgnUtil.writeToFile failed", e);
}
}
/**
* Converts the given model to SBGN, and writes in the specified output stream.
*
* @param model model to convert
* @param stream output stream to write
*/
public void writeSBGN(Model model, OutputStream stream)
{
Sbgn sbgn = createSBGN(model);
try {
JAXBContext context = JAXBContext.newInstance("org.sbgn.bindings");
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(sbgn, stream);
}
catch (JAXBException e) {
throw new RuntimeException("writeSBGN: JAXB marshalling failed", e);
}
}
/**
* Creates an Sbgn object from the given model.
*
* @param model model to convert to SBGN
* @return SBGN representation of the model
*/
public Sbgn createSBGN(Model model)
{
assert model.getLevel().equals(BioPAXLevel.L3) : "This method only supports L3 graphs";
glyphMap = new HashMap();
compartmentMap = new HashMap();
arcMap = new HashMap();
ubiqueSet = new HashSet();
// Create glyphs for Physical Entities
for (PhysicalEntity entity : model.getObjects(PhysicalEntity.class))
{
if (needsToBeCreatedInitially(entity))
{
createGlyph(entity);
}
}
// Create glyph for conversions and link with arcs
for (Conversion conv : model.getObjects(Conversion.class))
{
// For each conversion we check if we need to create a left-to-right and/or
// right-to-left process.
if (conv.getConversionDirection() == null ||
conv.getConversionDirection().equals(ConversionDirectionType.LEFT_TO_RIGHT) ||
(conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE) &&
useTwoGlyphsForReversibleConversion))
{
createProcessAndConnections(conv, ConversionDirectionType.LEFT_TO_RIGHT);
}
if (conv.getConversionDirection() != null &&
(conv.getConversionDirection().equals(ConversionDirectionType.RIGHT_TO_LEFT) ||
(conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE)) &&
useTwoGlyphsForReversibleConversion))
{
createProcessAndConnections(conv, ConversionDirectionType.RIGHT_TO_LEFT);
}
if (conv.getConversionDirection() != null &&
conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE) &&
!useTwoGlyphsForReversibleConversion)
{
createProcessAndConnections(conv, ConversionDirectionType.REVERSIBLE);
}
}
// Create glyph for template reactions and link with arcs
for (TemplateReaction tr : model.getObjects(TemplateReaction.class)) {
createProcessAndConnections(tr);
}
// Register created objects into sbgn construct
final Sbgn sbgn = factory.createSbgn();
org.sbgn.bindings.Map map = new org.sbgn.bindings.Map();
sbgn.setMap(map);
map.setLanguage(Language.PD.toString());
map.getGlyph().addAll(getRootGlyphs(glyphMap.values()));
map.getGlyph().addAll(getRootGlyphs(ubiqueSet));
map.getGlyph().addAll(compartmentMap.values());
map.getArc().addAll(arcMap.values());
if (doLayout && sbgn.getMap().getGlyph().size() < 1000) { //TODO: always skip layout for graphs >1000 glyphs?
(new SBGNLayoutManager()).createLayout(sbgn);
}
return sbgn;
}
//-- Section: Create molecules ----------------------------------------------------------------|
/**
* We don't want to represent every PhysicalEntity in SBGN. For instance if a Complex is nested
* under another Complex, and if it is not a participant of any interaction, we don't want to
* draw it.
*
* @param entity physical entity to check
* @return true if we will draw this entity in SBGN
*/
private boolean needsToBeCreatedInitially(PhysicalEntity entity)
{
boolean ret = true; //means - do create a node
if (entity instanceof Complex) {
Complex c = (Complex) entity;
if (c.getParticipantOf().isEmpty() && !c.getComponentOf().isEmpty()) {
// Inner complex will be created during creation of the top complex
ret = false;
}
}
else if (entity.getParticipantOf().isEmpty() && !entity.getComponentOf().isEmpty()) {
// Complex members will be created during creation of parent complex
ret = false;
}
else if(entity.getParticipantOf().isEmpty() && entity.getComponentOf().isEmpty()) {
// won't create a node for either a dangling, experimental form entity, or memberPhysicalEntity
if(!entity.getMemberPhysicalEntityOf().isEmpty())
log.debug("skip a memberPhysicalEntity (- also not a participant/component of another entity): "
+ entity.getUri());
else
log.debug("skip a dangling or experimental form phys. entity: " + entity.getUri());
ret = false;
}
else if (ubiqueDet != null && ubiqueDet.isUbique(entity)) {
// Ubiques will be created when they are used
ret = false;
}
return ret;
}
/**
* Creates a glyph representing the given PhysicalEntity.
*
* @param pe PhysicalEntity to represent
* @return the created glyph
*/
private Glyph createGlyph(PhysicalEntity pe)
{
String id = convertID(pe.getUri());
if (glyphMap.containsKey(id)) return glyphMap.get(id);
// Create its glyph and register
Glyph g = createGlyphBasics(pe);
glyphMap.put(g.getId(), g);
if (g.getClone() != null) ubiqueSet.add(g);
assignLocation(pe, g);
// Fill-in the complex members if this is a complex
if (pe instanceof Complex)
{
createComplexContent((Complex) pe);
}
return g;
}
/**
* Assigns compartmentRef of the glyph.
* @param pe Related PhysicalEntity
* @param g the glyph
*/
private void assignLocation(PhysicalEntity pe, Glyph g)
{
// Create compartment -- add this inside the compartment
Glyph loc = getCompartment(pe);
if (loc != null)
{
g.setCompartmentRef(loc);
}
}
/**
* This method creates a glyph for the given PhysicalEntity, sets its title and state variables
* if applicable.
*
* @param pe PhysicalEntity to represent
* @return the glyph
*/
private Glyph createGlyphBasics(PhysicalEntity pe)
{
return createGlyphBasics(pe, true);
}
/**
* This method creates a glyph for the given PhysicalEntity, sets its title and state variables
* if applicable.
*
* @param pe PhysicalEntity to represent
* @param idIsFinal if ID is final, then it is recorded for future reference
* @return the glyph
*/
private Glyph createGlyphBasics(PhysicalEntity pe, boolean idIsFinal)
{
String s = typeMatchMap.get(pe.getModelInterface());
Glyph g = factory.createGlyph();
g.setId(convertID(pe.getUri()));
g.setClazz(s);
// Set the label
Label label = factory.createLabel();
label.setText(findALabelForMolecule(pe));
g.setLabel(label);
// Detect if ubique
if (ubiqueDet != null && ubiqueDet.isUbique(pe))
{
g.setClone(factory.createGlyphClone());
}
// Put on state variables
List states = getInformation(pe);
g.getGlyph().addAll(states);
// Record the mapping
if (idIsFinal)
{
sbgn2BPMap.put(g.getId(), new HashSet());
sbgn2BPMap.get(g.getId()).add(pe.getUri());
}
return g;
}
/**
* Gets the representing glyph of the PhysicalEntity.
* @param pe PhysicalEntity to get its glyph
* @param linkID Edge id, used if the PhysicalEntity is ubique
* @return Representing glyph
*/
private Glyph getGlyphToLink(PhysicalEntity pe, String linkID)
{
if (ubiqueDet == null || !ubiqueDet.isUbique(pe))
{
return glyphMap.get(convertID(pe.getUri()));
}
else
{
// Create a new glyph for each use of ubique
Glyph g = createGlyphBasics(pe, false);
g.setId(convertID(pe.getUri()) + linkID);
sbgn2BPMap.put(g.getId(), new HashSet());
sbgn2BPMap.get(g.getId()).add(pe.getUri());
assignLocation(pe, g);
ubiqueSet.add(g);
return g;
}
}
/**
* Fills in the content of a complex.
*
* @param cx Complex to be filled
*/
private void createComplexContent(Complex cx)
{
Glyph cg = glyphMap.get(convertID(cx.getUri()));
if (flattenComplexContent)
{
for (PhysicalEntity mem : getFlattenedMembers(cx))
{
createComplexMember(mem, cg);
}
}
else
{
for (PhysicalEntity mem : cx.getComponent())
{
if (mem instanceof Complex)
{
addComplexAsMember((Complex) mem, cg);
}
else
{
createComplexMember(mem, cg);
}
}
}
}
/**
* Recursive method for creating the content of a complex. A complex may contain other complexes
* (bad practice), but SBGN needs them flattened. If an inner complex is empty, then we
* represent it using a glyph. Otherwise we represent only the members of this inner complex,
* merging them with the most outer complex.
*
* @param cx inner complex to add as member
* @param container glyph for most outer complex
*/
private void addComplexAsMember(Complex cx, Glyph container)
{
// Create a glyph for the inner complex
Glyph inner = createComplexMember(cx, container);
for (PhysicalEntity mem : cx.getComponent())
{
if (mem instanceof Complex)
{
// Recursive call for inner complexes
addComplexAsMember((Complex) mem, inner);
}
else
{
createComplexMember(mem, inner);
}
}
}
/**
* Gets the members of the Complex that needs to be displayed in a flattened view.
* @param cx to get members
* @return members to display
*/
private Set getFlattenedMembers(Complex cx)
{
Set set = new HashSet();
for (PhysicalEntity mem : cx.getComponent())
{
if (mem instanceof Complex)
{
if (!hasNonComplexMember((Complex) mem))
{
set.add(mem);
}
else
{
set.addAll(getFlattenedMembers((Complex) mem));
}
}
else set.add(mem);
}
return set;
}
/**
* Checks if a Complex contains any PhysicalEntity member which is not a Complex.
* @param cx to check
* @return true if there is a non-complex member
*/
private boolean hasNonComplexMember(Complex cx)
{
for (PhysicalEntity mem : cx.getComponent())
{
if (! (mem instanceof Complex)) return true;
else
{
if (hasNonComplexMember((Complex) mem)) return true;
}
}
return false;
}
/**
* Creates a glyph for the complex member.
*
* @param pe PhysicalEntity to represent as complex member
* @param container Glyph for the complex shell
*/
private Glyph createComplexMember(PhysicalEntity pe, Glyph container)
{
Glyph g = createGlyphBasics(pe, false);
container.getGlyph().add(g);
// A PhysicalEntity may appear in many complexes -- we identify the member using its complex
g.setId(g.getId() + "_" + container.getId());
glyphMap.put(g.getId(), g);
sbgn2BPMap.put(g.getId(), new HashSet());
sbgn2BPMap.get(g.getId()).add(pe.getUri());
return g;
}
/**
* Looks for the display name of this PhysicalEntity. If there is none, then it looks for the
* display name of its EntityReference. If still no name at hand, it tries the standard
* name, and then first element in name lists.
*
* A good BioPAX file will use a short and specific name (like HGNC symbols) as displayName.
*
* @param pe PhysicalEntity to find a name
* @return a name for labeling
*/
private String findALabelForMolecule(PhysicalEntity pe)
{
// Use gene symbol of PE
for (Xref xref : pe.getXref())
{
String sym = extractGeneSymbol(xref);
if (sym != null) return sym;
}
// Use gene symbol of ER
EntityReference er = null;
if (pe instanceof SimplePhysicalEntity)
{
er = ((SimplePhysicalEntity) pe).getEntityReference();
}
if (er != null)
{
for (Xref xref : er.getXref())
{
String sym = extractGeneSymbol(xref);
if (sym != null) return sym;
}
}
// Use display name of entity
String name = pe.getDisplayName();
if (name == null || name.trim().isEmpty())
{
if (er != null)
{
// Use display name of reference
name = er.getDisplayName();
}
if (name == null || name.trim().isEmpty())
{
// Use standard name of entity
name = pe.getStandardName();
if (name == null || name.trim().isEmpty())
{
if (er != null)
{
// Use standard name of reference
name = er.getStandardName();
}
if (name == null || name.trim().isEmpty())
{
if (!pe.getName().isEmpty())
{
// Use first name of entity
name = pe.getName().iterator().next();
}
else if (er != null && !er.getName().isEmpty())
{
// Use first name of reference
name = er.getName().iterator().next();
}
}
}
}
}
// Search for the shortest name of chemicals
if (pe instanceof SmallMolecule)
{
String shortName = getShortestName((SmallMolecule) pe);
if (shortName != null)
{
if (name == null || (shortName.length() < name.length() &&
!shortName.isEmpty()))
{
name = shortName;
}
}
}
if (name == null || name.trim().isEmpty())
{
// Don't leave it without a name
name = "noname";
}
return name;
}
/**
* Searches for the shortest name of the PhysicalEntity.
* @param spe entity to search in
* @return the shortest name
*/
private String getShortestName(SimplePhysicalEntity spe)
{
String name = null;
for (String s : spe.getName())
{
if (name == null || s.length() > name.length()) name = s;
}
EntityReference er = spe.getEntityReference();
if (er != null)
{
for (String s : er.getName())
{
if (name == null || s.length() > name.length()) name = s;
}
}
return name;
}
/**
* Searches for gene symbol in Xref.
* @param xref Xref to search
* @return gene symbol
*/
private String extractGeneSymbol(Xref xref)
{
if (xref.getDb() != null && (
xref.getDb().equalsIgnoreCase("HGNC Symbol") ||
xref.getDb().equalsIgnoreCase("Gene Symbol") ||
xref.getDb().equalsIgnoreCase("HGNC")))
{
String ref = xref.getId();
if (ref != null)
{
ref = ref.trim();
if (ref.contains(":")) ref = ref.substring(ref.indexOf(":") + 1);
if (ref.contains("_")) ref = ref.substring(ref.indexOf("_") + 1);
// if the reference is an HGNC ID, then convert it to a symbol
if (!HGNC.containsSymbol(ref) && Character.isDigit(ref.charAt(0)))
{
ref = HGNC.getSymbol(ref);
}
}
return ref;
}
return null;
}
/**
* Adds molecule type, and iterates over features of the entity and creates corresponding state
* variables. Ignores binding features and covalent-binding features.
*
* @param pe entity to collect features
* @return list of state variables
*/
private List getInformation(PhysicalEntity pe)
{
List list = new ArrayList();
// Add the molecule type before states if this is a nucleic acid
if (pe instanceof NucleicAcid)
{
Glyph g = factory.createGlyph();
g.setClazz(UNIT_OF_INFORMATION.getClazz());
Label label = factory.createLabel();
String s = "mt:";
s += ((pe instanceof Dna || pe instanceof DnaRegion) ? "DNA" :
(pe instanceof Rna || pe instanceof RnaRegion) ? "RNA" : "NuclAc");
label.setText(s);
g.setLabel(label);
list.add(g);
}
// Extract state variables
extractFeatures(pe.getFeature(), true, list);
extractFeatures(pe.getNotFeature(), false, list);
return list;
}
/**
* Converts the features in the given feature set. Adds a "!" in front of NOT features.
*
* @param features feature set
* @param normalFeature specifies the type of features -- normal feature = true,
* NOT feature = false
* @param list state variables
*/
private void extractFeatures(Set features, boolean normalFeature,
List list)
{
for (EntityFeature feature : features)
{
if (feature instanceof ModificationFeature || feature instanceof FragmentFeature)
{
Glyph stvar = factory.createGlyph();
stvar.setClazz(STATE_VARIABLE.getClazz());
Glyph.State state = featStrGen.createStateVar(feature, factory);
if (state != null)
{
// Add a "!" in front of NOT features
if (!normalFeature)
{
state.setValue("!" + state.getValue());
}
stvar.setState(state);
list.add(stvar);
}
}
}
}
//-- Section: Create compartments -------------------------------------------------------------|
private Glyph getCompartment(String name)
{
if (name == null)
return null;
name = name.toLowerCase();
if (compartmentMap.containsKey(name))
return compartmentMap.get(name);
Glyph comp = factory.createGlyph();
comp.setId(convertID(name));
Label label = factory.createLabel();
label.setText(name);
comp.setLabel(label);
comp.setClazz(COMPARTMENT.getClazz());
compartmentMap.put(name, comp);
return comp;
}
/**
* Gets the compartment of the given PhysicalEntity.
*
* @param pe PhysicalEntity to look for its compartment
* @return name of compartment or null if there is none
*/
private Glyph getCompartment(PhysicalEntity pe)
{
CellularLocationVocabulary cl = pe.getCellularLocation();
if (cl != null && !cl.getTerm().isEmpty())
{
String name = null;
// get a cv term,
// ignoring IDs (should not be there but happens)
for(String term : cl.getTerm()) {
term = term.toLowerCase();
if(!term.matches("(go|so|mi|bto|cl|pato|mod):")) {
name = term;
break;
}
}
return getCompartment(name);
} else
return null;
}
//-- Section: Create reactions ----------------------------------------------------------------|
/**
* Creates a representation for Conversion.
*
* @param cnv the conversion
* @param direction direction of the conversion to create
*/
private void createProcessAndConnections(Conversion cnv,
ConversionDirectionType direction)
{
assert cnv.getConversionDirection() == null ||
cnv.getConversionDirection().equals(direction) ||
cnv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE);
// create the process for the conversion in that direction
Glyph process = factory.createGlyph();
process.setClazz(PROCESS.getClazz());
process.setId(convertID(cnv.getUri()) + direction);
glyphMap.put(process.getId(), process);
// Determine input and output sets
Set input = direction.equals(ConversionDirectionType.RIGHT_TO_LEFT) ?
cnv.getRight() : cnv.getLeft();
Set output = direction.equals(ConversionDirectionType.RIGHT_TO_LEFT) ?
cnv.getLeft() : cnv.getRight();
// Create input and outputs ports for the process
addPorts(process);
Map stoic = getStoichiometry(cnv);
// Associate inputs to input port
for (PhysicalEntity pe : input)
{
Glyph g = getGlyphToLink(pe, process.getId());
createArc(g, process.getPort().get(0), direction == ConversionDirectionType.REVERSIBLE ?
PRODUCTION.getClazz() : CONSUMPTION.getClazz(), stoic.get(pe));
}
// Associate outputs to output port
for (PhysicalEntity pe : output)
{
Glyph g = getGlyphToLink(pe, process.getId());
createArc(process.getPort().get(1), g, PRODUCTION.getClazz(), stoic.get(pe));
}
// Associate controllers
for (Control ctrl : cnv.getControlledOf())
{
// If there is a direction mismatch between the process and the control, just skip it
if (ctrl instanceof Catalysis)
{
CatalysisDirectionType catDir = ((Catalysis) ctrl).getCatalysisDirection();
if (catDir != null)
{
if ((catDir.equals(CatalysisDirectionType.LEFT_TO_RIGHT) &&
direction.equals(ConversionDirectionType.RIGHT_TO_LEFT)) ||
(catDir.equals(CatalysisDirectionType.RIGHT_TO_LEFT) &&
direction.equals(ConversionDirectionType.LEFT_TO_RIGHT)))
{
// Skip
continue;
}
}
}
Glyph g = createControlStructure(ctrl);
if (g != null) createArc(g, process, getControlType(ctrl), null);
}
// Record mapping
sbgn2BPMap.put(process.getId(), new HashSet());
sbgn2BPMap.get(process.getId()).add(cnv.getUri());
}
/**
* Gets the map of stoichiometry coefficients of participants.
* @param conv the conversion
* @return map from physical entities to their stoichiometry
*/
private Map getStoichiometry(Conversion conv)
{
Map map = new HashMap();
for (Stoichiometry stoc : conv.getParticipantStoichiometry())
{
map.put(stoc.getPhysicalEntity(), stoc);
}
return map;
}
/**
* Creates a representation for TemplateReaction.
*
* @param tr template reaction
*/
private void createProcessAndConnections(TemplateReaction tr)
{
// create the process for the reaction
Glyph process = factory.createGlyph();
process.setClazz(PROCESS.getClazz());
process.setId(convertID(tr.getUri()));
glyphMap.put(process.getId(), process);
// Add input and output ports
addPorts(process);
// Create a source-and-sink as the input
Glyph sas = factory.createGlyph();
sas.setClazz(SOURCE_AND_SINK.getClazz());
sas.setId("SAS_For_" + process.getId());
glyphMap.put(sas.getId(), sas);
createArc(sas, process.getPort().get(0), CONSUMPTION.getClazz(), null);
// Associate products
for (PhysicalEntity pe : tr.getProduct())
{
Glyph g = getGlyphToLink(pe, process.getId());
createArc(process.getPort().get(1), g, PRODUCTION.getClazz(), null);
}
// Associate controllers
for (Control ctrl : tr.getControlledOf())
{
Glyph g = createControlStructure(ctrl);
if (g != null) createArc(g, process, getControlType(ctrl), null);
}
// Record mapping
sbgn2BPMap.put(process.getId(), new HashSet());
sbgn2BPMap.get(process.getId()).add(tr.getUri());
}
/**
* Creates or gets the glyph to connect to the control arc.
*
* @param ctrl Control to represent
* @return glyph representing the controller tree
*/
private Glyph createControlStructure(Control ctrl)
{
Glyph cg;
Set controllers = getControllers(ctrl);
// If no representable controller found, skip this control
if (controllers.isEmpty()) cg = null;
// If there is only one controller with no modulator, put an arc for controller
else if (controllers.size() == 1 && getControllerSize(ctrl.getControlledOf()) == 0)
{
cg = getGlyphToLink(controllers.iterator().next(), convertID(ctrl.getUri()));
}
else
{
// This list will contain handles for each participant of the AND structure
List toConnect = new ArrayList();
// Bundle controllers if necessary
Glyph gg = handlePEGroup(controllers, convertID(ctrl.getUri()));
if(gg != null)
toConnect.add(gg);
// Create handles for each controller
for (Control ctrl2 : ctrl.getControlledOf())
{
Glyph g = createControlStructure(ctrl2);
if (g != null)
{
// If the control is negative, add a NOT in front of it
if (getControlType(ctrl2).equals(INHIBITION.getClazz()))
{
g = addNOT(g);
}
toConnect.add(g);
}
}
// Handle co-factors of catalysis
if (ctrl instanceof Catalysis)
{
Set cofs = ((Catalysis) ctrl).getCofactor();
Glyph g = handlePEGroup(cofs, convertID(ctrl.getUri()));
if (g != null)
toConnect.add(g);
}
if (toConnect.isEmpty())
return null;
else if (toConnect.size() == 1)
{
cg = toConnect.iterator().next();
}
else
{
cg = connectWithAND(toConnect);
}
}
return cg;
}
/**
* Prepares the necessary construct for adding the given PhysicalEntity set to the Control
* being drawn.
*
* @param pes entities to use in control
* @return the glyph to connect to the appropriate place
*/
private Glyph handlePEGroup(Set pes, String context)
{
int sz = pes.size();
if (sz > 1)
{
List gs = getGlyphsOfPEs(pes, context);
return connectWithAND(gs);
}
else if (sz == 1 && glyphMap.containsKey(convertID(pes.iterator().next().getUri())))
{
return getGlyphToLink(pes.iterator().next(), context);
}
//'pes' was empty
return null;
}
/**
* Gets the glyphs of the given set of PhysicalEntity objects. Does not create anything.
*
* @param pes entities to get their glyphs
* @return glyphs of entities
*/
private List getGlyphsOfPEs(Set pes, String context)
{
List gs = new ArrayList();
for (PhysicalEntity pe : pes)
{
if (glyphMap.containsKey(convertID(pe.getUri())))
{
gs.add(getGlyphToLink(pe, context));
}
}
return gs;
}
/**
* Creates an AND glyph downstream of the given glyphs.
*
* @param gs upstream glyph list
* @return AND glyph
*/
private Glyph connectWithAND(List gs)
{
// Compose an ID for the AND glyph
String id = "";
for (Glyph g : gs)
{
id = id + (id.length() > 0 ? "-AND-" : "") + g.getId();
}
// Create the AND glyph if not exists
Glyph and;
if (!glyphMap.containsKey(id))
{
and = factory.createGlyph();
and.setClazz(AND.getClazz());
and.setId(id);
glyphMap.put(and.getId(), and);
}
else
{
and = glyphMap.get(id);
}
// Connect upstream to the AND glyph
for (Glyph g : gs)
{
createArc(g, and, LOGIC_ARC.getClazz(), null);
}
return and;
}
/**
* Adds a NOT glyph next to the given glyph.
*
* @param g glyph to add NOT
* @return NOT glyph
*/
private Glyph addNOT(Glyph g)
{
// Assemble an ID for the NOT glyph
String id = "NOT-" + g.getId();
// Find or create the NOT glyph
Glyph not;
if (!glyphMap.containsKey(id))
{
not = factory.createGlyph();
not.setId(id);
not.setClazz(NOT.getClazz());
glyphMap.put(not.getId(), not);
}
else
{
not = glyphMap.get(id);
}
// Connect the glyph and NOT
createArc(g, not, LOGIC_ARC.getClazz(), null);
return not;
}
/**
* Converts the control type of the Control to the SBGN classes.
*
* @param ctrl Control to get its type
* @return SBGN type of the Control
*/
private String getControlType(Control ctrl)
{
if (ctrl instanceof Catalysis)
{
// Catalysis has its own class
return CATALYSIS.getClazz();
}
ControlType type = ctrl.getControlType();
if (type == null)
{
// Use stimulation as the default control type
return STIMULATION.getClazz();
}
// Map control type to stimulation or inhibition
switch (type)
{
case ACTIVATION:
case ACTIVATION_ALLOSTERIC:
case ACTIVATION_NONALLOSTERIC:
case ACTIVATION_UNKMECH: return STIMULATION.getClazz();
case INHIBITION:
case INHIBITION_ALLOSTERIC:
case INHIBITION_OTHER:
case INHIBITION_UNKMECH:
case INHIBITION_COMPETITIVE:
case INHIBITION_IRREVERSIBLE:
case INHIBITION_UNCOMPETITIVE:
case INHIBITION_NONCOMPETITIVE: return INHIBITION.getClazz();
}
throw new RuntimeException("Invalid control type: " + type);
}
/**
* Gets the size of representable Controller of this set of Controls.
*
* @param ctrlSet Controls to check their controllers
* @return size of representable controllers
*/
private int getControllerSize(Set ctrlSet)
{
int size = 0;
for (Control ctrl : ctrlSet)
{
size += getControllers(ctrl).size();
}
return size;
}
/**
* Gets the size of representable Controller of this Control.
*
* @param ctrl Control to check its controllers
* @return size of representable controllers
*/
private Set getControllers(Control ctrl)
{
Set controllers = new HashSet();
for (Controller clr : ctrl.getController())
{
if (clr instanceof PhysicalEntity && glyphMap.containsKey(convertID(clr.getUri())))
{
controllers.add((PhysicalEntity) clr);
}
}
return controllers;
}
/**
* Adds input and output ports to the glyph.
*
* @param g glyph to add ports
*/
private void addPorts(Glyph g)
{
Port inputPort = factory.createPort();
Port outputPort = factory.createPort();
inputPort.setId(g.getId() + "-input");
outputPort.setId(g.getId() + "-output");
g.getPort().add(inputPort);
g.getPort().add(outputPort);
}
//-- Section: Create arcs ---------------------------------------------------------------------|
/**
* Creates an arc from the source to the target, and sets its class to the specified clazz.
* Puts the new arc in the sullied arcMap.
*
* @param source source of the arc -- either Glyph or Port
* @param target target of the arc -- either Glyph or Port
* @param clazz class of the arc
*/
private void createArc(Object source, Object target, String clazz, Stoichiometry stoic)
{
assert source instanceof Glyph || source instanceof Port : "source = " + source;
assert target instanceof Glyph || target instanceof Port : "target = " + target;
Arc arc = factory.createArc();
arc.setSource(source);
arc.setTarget(target);
arc.setClazz(clazz);
String sourceID = source instanceof Glyph ?
((Glyph) source).getId() : ((Port) source).getId();
String targetID = target instanceof Glyph ?
((Glyph) target).getId() : ((Port) target).getId();
arc.setId(sourceID + "--to--" + targetID);
if (stoic != null && stoic.getStoichiometricCoefficient() != 1F)
{
Glyph card = factory.createGlyph();
card.setClazz(CARDINALITY.getClazz());
Label label = factory.createLabel();
label.setText(new DecimalFormat("0.##").format(stoic.getStoichiometricCoefficient()));
card.setLabel(label);
arc.getGlyph().add(card);
}
Arc.Start start = new Arc.Start();
start.setX(0);
start.setY(0);
arc.setStart(start);
Arc.End end = new Arc.End();
end.setX(0);
end.setY(0);
arc.setEnd(end);
arcMap.put(arc.getId(), arc);
}
/**
* Collects root-level glyphs in the given glyph collection.
*
* @param glyphCol glyph collection to search
* @return set of roots
*/
private Set getRootGlyphs(Collection glyphCol)
{
Set root = new HashSet(glyphCol);
Set children = new HashSet();
for (Glyph glyph : glyphCol)
{
addChildren(glyph, children);
}
root.removeAll(children);
return root;
}
/**
* Adds children of this glyph to the specified set recursively.
* @param glyph to collect children
* @param set to add
*/
private void addChildren(Glyph glyph, Set set)
{
for (Glyph child : glyph.getGlyph())
{
set.add(child);
addChildren(child, set);
}
}
/**
* Gets the mapping from SBGN IDs to BioPAX IDs. This mapping is currently one-to-many, but has
* potential to become many-to-many in the future.
* @return sbgn-to-biopax mapping
*/
public Map> getSbgn2BPMap()
{
return sbgn2BPMap;
}
private String convertID(String id)
{
return id.replaceAll("[^-\\w]", "_");
}
//-- Section: Static initialization -----------------------------------------------------------|
/**
* Initializes resources.
*/
static
{
factory = new ObjectFactory();
typeMatchMap = new HashMap, String>();
typeMatchMap.put(Protein.class, MACROMOLECULE.getClazz());
typeMatchMap.put(SmallMolecule.class, SIMPLE_CHEMICAL.getClazz());
typeMatchMap.put(Dna.class, NUCLEIC_ACID_FEATURE.getClazz());
typeMatchMap.put(Rna.class, NUCLEIC_ACID_FEATURE.getClazz());
typeMatchMap.put(DnaRegion.class, NUCLEIC_ACID_FEATURE.getClazz());
typeMatchMap.put(RnaRegion.class, NUCLEIC_ACID_FEATURE.getClazz());
typeMatchMap.put(NucleicAcid.class, NUCLEIC_ACID_FEATURE.getClazz());
typeMatchMap.put(PhysicalEntity.class, UNSPECIFIED_ENTITY.getClazz());
typeMatchMap.put(SimplePhysicalEntity.class, UNSPECIFIED_ENTITY.getClazz());
typeMatchMap.put(Complex.class, COMPLEX.getClazz());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy