001package org.biopax.paxtools.io.sbgn; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.biopax.paxtools.io.sbgn.idmapping.HGNC; 006import org.biopax.paxtools.model.BioPAXElement; 007import org.biopax.paxtools.model.BioPAXLevel; 008import org.biopax.paxtools.model.Model; 009import org.biopax.paxtools.model.level3.*; 010import org.sbgn.Language; 011import org.sbgn.SbgnUtil; 012import org.sbgn.bindings.*; 013 014import javax.xml.bind.JAXBContext; 015import javax.xml.bind.JAXBException; 016import javax.xml.bind.Marshaller; 017import java.io.File; 018import java.io.OutputStream; 019import java.io.PrintWriter; 020import java.text.DecimalFormat; 021import java.util.*; 022import java.util.Map; 023 024import static org.sbgn.GlyphClazz.*; 025import static org.sbgn.ArcClazz.*; 026 027/** 028 * This class converts BioPAX L3 model into SBGN PD. It does not layout the objects, leaves location 029 * information unassigned. 030 * 031 * This version ignores several BioPAX L3 features during conversion: 032 * <ul> 033 * <li>Parent-child relationship between physical entities</li> 034 * <li>Parent-child relationship between entity references</li> 035 * <li>Binding features and covalent binding features of physical entities</li> 036 * </ul> 037 * 038 * Also note that: 039 * <ul> 040 * <li>Compartment is just a controlled vocabulary in BioPAX, so nesting and neighborhood relations 041 * between compartments are not handled here.</li> 042 * <li>Control structures in BioPAX and in SBGN PD are a little different. We use AND and NOT 043 * glyphs to approximate controls in BioPAX. However, ANDing everything is not really proper because 044 * BioPAX do not imply a logical operator between controllers.</li> 045 * </ul> 046 * 047 * @author Ozgun Babur 048 */ 049public class L3ToSBGNPDConverter 050{ 051 //-- Section: Static fields -------------------------------------------------------------------| 052 053 /** 054 * Log for logging. 055 */ 056 private static final Log log = LogFactory.getLog(L3ToSBGNPDConverter.class); 057 058 /** 059 * Ubique label. 060 */ 061 public static final String IS_UBIQUE = "IS_UBIQUE"; 062 063 /** 064 * A matching between physical entities and SBGN classes. 065 */ 066 private static Map<Class<? extends BioPAXElement>, String> typeMatchMap; 067 068 /** 069 * For creating SBGN objects. 070 */ 071 private static ObjectFactory factory; 072 073 //-- Section: Instance variables --------------------------------------------------------------| 074 075 /** 076 * This class is used for detecting ubiques. 077 */ 078 protected UbiqueDetector ubiqueDet; 079 080 /** 081 * This class is used for generating short printable strings (text in info boxes) from 082 * recognized entity features. 083 */ 084 protected FeatureDecorator featStrGen; 085 086 /** 087 * Flag to run a layout before writing down the sbgn. 088 */ 089 protected boolean doLayout; 090 091 /** 092 * Mapping from SBGN IDs to the IDs of the related objects in BioPAX. 093 */ 094 protected Map<String, Set<String>> sbgn2BPMap; 095 096 /** 097 * Option to flatten nested complexes. 098 */ 099 protected boolean flattenComplexContent; 100 101 /** 102 * SBGN process glyph can be used to show reversible reactions. In that case two ports of the 103 * process will only have product glyphs. However, this creates an incompatibility with BioPAX: 104 * reversible biochemical reactions can have catalysis with a direction. But if we use a single 105 * glyph for the process and direct that catalysis to it, then the direction of the catalysis 106 * will be lost. If we use two process nodes for the reversible reaction (one for left-to-right 107 * and another for right-to-left), then we can direct the directed catalysis to only the 108 * relevant process glyph. 109 * 110 * Also note that the layout do not support ports. If layout is run, the ports are removed. In 111 * that case it will be impossible to distinguish left and right components of the reversible 112 * process glyph because they will all be product edges. 113 */ 114 protected boolean useTwoGlyphsForReversibleConversion; 115 116 /** 117 * ID to glyph map. 118 */ 119 Map<String, Glyph> glyphMap; 120 121 /** 122 * ID to Arc map 123 */ 124 Map<String, Arc> arcMap; 125 126 /** 127 * ID to compartment map. 128 */ 129 Map<String, Glyph> compartmentMap; 130 131 /** 132 * Set of ubiquitous molecules. 133 */ 134 Set<Glyph> ubiqueSet; 135 136 //-- Section: Public methods ------------------------------------------------------------------| 137 138 /** 139 * Empty constructor. 140 */ 141 public L3ToSBGNPDConverter() 142 { 143 this(null, null, true); 144 } 145 146 /** 147 * Constructor with parameters. 148 * @param ubiqueDet Ubique detector class 149 * @param featStrGen feature string generator class 150 * @param doLayout whether we want to perform layout after SBGN creation. 151 */ 152 public L3ToSBGNPDConverter(UbiqueDetector ubiqueDet, FeatureDecorator featStrGen, 153 boolean doLayout) 154 { 155 this.ubiqueDet = ubiqueDet; 156 this.featStrGen = featStrGen; 157 this.doLayout = doLayout; 158 159 if (this.featStrGen == null) 160 this.featStrGen = new CommonFeatureStringGenerator(); 161 162 this.useTwoGlyphsForReversibleConversion = true; 163 this.sbgn2BPMap = new HashMap<String, Set<String>>(); 164 this.flattenComplexContent = true; 165 } 166 167 /** 168 * Getter class for the parameter useTwoGlyphsForReversibleConversion. 169 * @return whether use two glyphs for the reversible conversion 170 */ 171 public boolean isUseTwoGlyphsForReversibleConversion() 172 { 173 return useTwoGlyphsForReversibleConversion; 174 } 175 176 /** 177 * Sets the option to use two glyphs for the reversible conversion. 178 * @param useTwoGlyphsForReversibleConversion give true if use two glyphs 179 */ 180 public void setUseTwoGlyphsForReversibleConversion(boolean useTwoGlyphsForReversibleConversion) 181 { 182 this.useTwoGlyphsForReversibleConversion = useTwoGlyphsForReversibleConversion; 183 } 184 185 public boolean isFlattenComplexContent() 186 { 187 return flattenComplexContent; 188 } 189 190 public void setFlattenComplexContent(boolean flattenComplexContent) 191 { 192 this.flattenComplexContent = flattenComplexContent; 193 } 194 195 /** 196 * Converts the given model to SBGN, and writes in the specified file. 197 * 198 * @param model model to convert 199 * @param file file to write 200 */ 201 public void writeSBGN(Model model, String file) 202 { 203 // Create the model 204 Sbgn sbgn = createSBGN(model); 205 206 // Write in file 207 208 try 209 { 210 SbgnUtil.writeToFile(sbgn, new File(file)); 211 } 212 catch (JAXBException e) 213 { 214 if (log.isErrorEnabled()) log.error(e.getCause(), e); 215 } 216 } 217 218 /** 219 * Converts the given model to SBGN, and writes in the specified output stream. 220 * 221 * @param model model to convert 222 * @param stream output stream to write 223 */ 224 public void writeSBGN(Model model, OutputStream stream) 225 { 226 // Create the model 227 Sbgn sbgn = createSBGN(model); 228 229 // Write in file 230 231 try 232 { 233 sbgn.toString(); 234 JAXBContext context = JAXBContext.newInstance("org.sbgn.bindings"); 235 Marshaller marshaller = context.createMarshaller(); 236 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 237 marshaller.marshal(sbgn, stream); 238 } 239 catch (JAXBException e) 240 { 241 if (log.isErrorEnabled()) log.error(e.getCause(), e); 242 e.printStackTrace(new PrintWriter(stream)); 243 } 244 } 245 246 /** 247 * Creates an Sbgn object from the given model. 248 * 249 * @param model model to convert to SBGN 250 * @return SBGN representation of the model 251 */ 252 public Sbgn createSBGN(Model model) 253 { 254 assert model.getLevel().equals(BioPAXLevel.L3) : "This method only supports L3 graphs"; 255 256 glyphMap = new HashMap<String, Glyph>(); 257 compartmentMap = new HashMap<String, Glyph>(); 258 arcMap = new HashMap<String, Arc>(); 259 ubiqueSet = new HashSet<Glyph>(); 260 261 // Create glyphs for Physical Entities 262 263 for (PhysicalEntity entity : model.getObjects(PhysicalEntity.class)) 264 { 265 if (needsToBeCreatedInitially(entity)) 266 { 267 createGlyph(entity); 268 } 269 } 270 271 // Create glyph for conversions and link with arcs 272 273 for (Conversion conv : model.getObjects(Conversion.class)) 274 { 275 // For each conversion we check if we need to create a left-to-right and/or 276 // right-to-left process. 277 278 if (conv.getConversionDirection() == null || 279 conv.getConversionDirection().equals(ConversionDirectionType.LEFT_TO_RIGHT) || 280 (conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE) && 281 useTwoGlyphsForReversibleConversion)) 282 { 283 createProcessAndConnections(conv, ConversionDirectionType.LEFT_TO_RIGHT); 284 } 285 286 if (conv.getConversionDirection() != null && 287 (conv.getConversionDirection().equals(ConversionDirectionType.RIGHT_TO_LEFT) || 288 (conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE)) && 289 useTwoGlyphsForReversibleConversion)) 290 { 291 createProcessAndConnections(conv, ConversionDirectionType.RIGHT_TO_LEFT); 292 } 293 294 if (conv.getConversionDirection() != null && 295 conv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE) && 296 !useTwoGlyphsForReversibleConversion) 297 { 298 createProcessAndConnections(conv, ConversionDirectionType.REVERSIBLE); 299 } 300 } 301 302 // Create glyph for template reactions and link with arcs 303 304 for (TemplateReaction tr : model.getObjects(TemplateReaction.class)) 305 { 306 createProcessAndConnections(tr); 307 } 308 309 // Register created objects into sbgn construct 310 311 Sbgn sbgn = factory.createSbgn(); 312 org.sbgn.bindings.Map map = new org.sbgn.bindings.Map(); 313 sbgn.setMap(map); 314 map.setLanguage(Language.PD.toString()); 315 316 map.getGlyph().addAll(getRootGlyphs(glyphMap.values())); 317 map.getGlyph().addAll(getRootGlyphs(ubiqueSet)); 318 map.getGlyph().addAll(compartmentMap.values()); 319 map.getArc().addAll(arcMap.values()); 320 321 if (doLayout) 322 { 323 /*------------------ChiLay layout modification ----------------------------*/ 324 //Apply Layout to SBGN objects 325 SBGNLayoutManager coseLayoutManager = new SBGNLayoutManager(); 326 sbgn = coseLayoutManager.createLayout(sbgn); 327 } 328 329 return sbgn; 330 } 331 332 //-- Section: Create molecules ----------------------------------------------------------------| 333 334 /** 335 * We don't want to represent every PhysicalEntity in SBGN. For instance if a Complex is nested 336 * under another Complex, and if it is not a participant of any interaction, we don't want to 337 * draw it. 338 * 339 * @param entity physical entity to check 340 * @return true if we will draw this entity in SBGN 341 */ 342 private boolean needsToBeCreatedInitially(PhysicalEntity entity) 343 { 344 // Inner complexes will be created during creation of the top complex 345 if (entity instanceof Complex) 346 { 347 Complex c = (Complex) entity; 348 if (c.getParticipantOf().isEmpty() && !c.getComponentOf().isEmpty()) 349 { 350 return false; 351 } 352 } 353 // Complex members will be created during creation of parent complex 354 else if (entity.getParticipantOf().isEmpty() && !entity.getComponentOf().isEmpty()) 355 { 356 return false; 357 } 358 // Ubiques will be created when they are used 359 else if (ubiqueDet != null && ubiqueDet.isUbique(entity)) 360 { 361 return false; 362 } 363 return true; 364 } 365 366 /** 367 * Creates a glyph representing the given PhysicalEntity. 368 * 369 * @param pe PhysicalEntity to represent 370 * @return the created glyph 371 */ 372 private Glyph createGlyph(PhysicalEntity pe) 373 { 374 String id = convertID(pe.getRDFId()); 375 if (glyphMap.containsKey(id)) return glyphMap.get(id); 376 377 // Create its glyph and register 378 379 Glyph g = createGlyphBasics(pe); 380 glyphMap.put(g.getId(), g); 381 if (g.getClone() != null) ubiqueSet.add(g); 382 383 assignLocation(pe, g); 384 385 // Fill-in the complex members if this is a complex 386 387 if (pe instanceof Complex) 388 { 389 createComplexContent((Complex) pe); 390 } 391 392 return g; 393 } 394 395 /** 396 * Assigns compartmentRef of the glyph. 397 * @param pe Related PhysicalEntity 398 * @param g the glyph 399 */ 400 private void assignLocation(PhysicalEntity pe, Glyph g) 401 { 402 // Create compartment -- add this inside the compartment 403 404 Glyph loc = getCompartment(getCompartment(pe)); 405 if (loc != null) 406 { 407 g.setCompartmentRef(loc); 408 } 409 } 410 411 /** 412 * This method creates a glyph for the given PhysicalEntity, sets its title and state variables 413 * if applicable. 414 * 415 * @param pe PhysicalEntity to represent 416 * @return the glyph 417 */ 418 private Glyph createGlyphBasics(PhysicalEntity pe) 419 { 420 return createGlyphBasics(pe, true); 421 } 422 /** 423 * This method creates a glyph for the given PhysicalEntity, sets its title and state variables 424 * if applicable. 425 * 426 * @param pe PhysicalEntity to represent 427 * @param idIsFinal if ID is final, then it is recorded for future reference 428 * @return the glyph 429 */ 430 private Glyph createGlyphBasics(PhysicalEntity pe, boolean idIsFinal) 431 { 432 String s = typeMatchMap.get(pe.getModelInterface()); 433 434 Glyph g = factory.createGlyph(); 435 g.setId(convertID(pe.getRDFId())); 436 g.setClazz(s); 437 438 // Set the label 439 440 Label label = factory.createLabel(); 441 label.setText(findALabelForMolecule(pe)); 442 g.setLabel(label); 443 444 // Detect if ubique 445 446 if (ubiqueDet != null && ubiqueDet.isUbique(pe)) 447 { 448 g.setClone(factory.createGlyphClone()); 449 } 450 451 // Put on state variables 452 453 List<Glyph> states = getInformation(pe); 454 g.getGlyph().addAll(states); 455 456 // Record the mapping 457 if (idIsFinal) 458 { 459 sbgn2BPMap.put(g.getId(), new HashSet<String>()); 460 sbgn2BPMap.get(g.getId()).add(pe.getRDFId()); 461 } 462 return g; 463 } 464 465 /** 466 * Gets the representing glyph of the PhysicalEntity. 467 * @param pe PhysicalEntity to get its glyph 468 * @param linkID Edge id, used if the PhysicalEntity is ubique 469 * @return Representing glyph 470 */ 471 private Glyph getGlyphToLink(PhysicalEntity pe, String linkID) 472 { 473 if (ubiqueDet == null || !ubiqueDet.isUbique(pe)) 474 { 475 return glyphMap.get(convertID(pe.getRDFId())); 476 } 477 else 478 { 479 // Create a new glyph for each use of ubique 480 Glyph g = createGlyphBasics(pe, false); 481 g.setId(convertID(pe.getRDFId()) + linkID); 482 483 sbgn2BPMap.put(g.getId(), new HashSet<String>()); 484 sbgn2BPMap.get(g.getId()).add(pe.getRDFId()); 485 486 assignLocation(pe, g); 487 ubiqueSet.add(g); 488 return g; 489 } 490 } 491 492 /** 493 * Fills in the content of a complex. 494 * 495 * @param cx Complex to be filled 496 */ 497 private void createComplexContent(Complex cx) 498 { 499 Glyph cg = glyphMap.get(convertID(cx.getRDFId())); 500 501 if (flattenComplexContent) 502 { 503 for (PhysicalEntity mem : getFlattenedMembers(cx)) 504 { 505 createComplexMember(mem, cg); 506 } 507 } 508 else 509 { 510 for (PhysicalEntity mem : cx.getComponent()) 511 { 512 if (mem instanceof Complex) 513 { 514 addComplexAsMember((Complex) mem, cg); 515 } 516 else 517 { 518 createComplexMember(mem, cg); 519 } 520 } 521 } 522 } 523 524 /** 525 * Recursive method for creating the content of a complex. A complex may contain other complexes 526 * (bad practice), but SBGN needs them flattened. If an inner complex is empty, then we 527 * represent it using a glyph. Otherwise we represent only the members of this inner complex, 528 * merging them with the most outer complex. 529 * 530 * @param cx inner complex to add as member 531 * @param container glyph for most outer complex 532 */ 533 private void addComplexAsMember(Complex cx, Glyph container) 534 { 535 // Create a glyph for the inner complex 536 Glyph inner = createComplexMember(cx, container); 537 538 for (PhysicalEntity mem : cx.getComponent()) 539 { 540 if (mem instanceof Complex) 541 { 542 // Recursive call for inner complexes 543 addComplexAsMember((Complex) mem, inner); 544 } 545 else 546 { 547 createComplexMember(mem, inner); 548 } 549 } 550 } 551 552 /** 553 * Gets the members of the Complex that needs to be displayed in a flattened view. 554 * @param cx to get members 555 * @return members to display 556 */ 557 private Set<PhysicalEntity> getFlattenedMembers(Complex cx) 558 { 559 Set<PhysicalEntity> set = new HashSet<PhysicalEntity>(); 560 561 for (PhysicalEntity mem : cx.getComponent()) 562 { 563 if (mem instanceof Complex) 564 { 565 if (!hasNonComplexMember((Complex) mem)) 566 { 567 set.add(mem); 568 } 569 else 570 { 571 set.addAll(getFlattenedMembers((Complex) mem)); 572 } 573 } 574 else set.add(mem); 575 } 576 return set; 577 } 578 579 /** 580 * Checks if a Complex contains any PhysicalEntity member which is not a Complex. 581 * @param cx to check 582 * @return true if there is a non-complex member 583 */ 584 private boolean hasNonComplexMember(Complex cx) 585 { 586 for (PhysicalEntity mem : cx.getComponent()) 587 { 588 if (! (mem instanceof Complex)) return true; 589 else 590 { 591 if (hasNonComplexMember((Complex) mem)) return true; 592 } 593 } 594 return false; 595 } 596 597 /** 598 * Creates a glyph for the complex member. 599 * 600 * @param pe PhysicalEntity to represent as complex member 601 * @param container Glyph for the complex shell 602 */ 603 private Glyph createComplexMember(PhysicalEntity pe, Glyph container) 604 { 605 Glyph g = createGlyphBasics(pe, false); 606 container.getGlyph().add(g); 607 608 // A PhysicalEntity may appear in many complexes -- we identify the member using its complex 609 g.setId(g.getId() + "_" + container.getId()); 610 611 glyphMap.put(g.getId(), g); 612 613 sbgn2BPMap.put(g.getId(), new HashSet<String>()); 614 sbgn2BPMap.get(g.getId()).add(pe.getRDFId()); 615 616 return g; 617 } 618 619 /** 620 * Looks for the display name of this PhysicalEntity. If there is none, then it looks for the 621 * display name of its EntityReference. If still no name at hand, it tries the standard 622 * name, and then first element in name lists. 623 * 624 * A good BioPAX file will use a short and specific name (like HGNC symbols) as displayName. 625 * 626 * @param pe PhysicalEntity to find a name 627 * @return a name for labeling 628 */ 629 private String findALabelForMolecule(PhysicalEntity pe) 630 { 631 // Use gene symbol of PE 632 633 for (Xref xref : pe.getXref()) 634 { 635 String sym = extractGeneSymbol(xref); 636 if (sym != null) return sym; 637 } 638 639 // Use gene symbol of ER 640 641 EntityReference er = null; 642 643 if (pe instanceof SimplePhysicalEntity) 644 { 645 er = ((SimplePhysicalEntity) pe).getEntityReference(); 646 } 647 648 if (er != null) 649 { 650 for (Xref xref : er.getXref()) 651 { 652 String sym = extractGeneSymbol(xref); 653 if (sym != null) return sym; 654 } 655 } 656 657 // Use display name of entity 658 String name = pe.getDisplayName(); 659 660 if (name == null || name.trim().isEmpty()) 661 { 662 if (er != null) 663 { 664 // Use display name of reference 665 name = er.getDisplayName(); 666 } 667 668 if (name == null || name.trim().isEmpty()) 669 { 670 // Use standard name of entity 671 name = pe.getStandardName(); 672 673 if (name == null || name.trim().isEmpty()) 674 { 675 if (er != null) 676 { 677 // Use standard name of reference 678 name = er.getStandardName(); 679 } 680 681 if (name == null || name.trim().isEmpty()) 682 { 683 if (!pe.getName().isEmpty()) 684 { 685 // Use first name of entity 686 name = pe.getName().iterator().next(); 687 } 688 else if (er != null && !er.getName().isEmpty()) 689 { 690 // Use first name of reference 691 name = er.getName().iterator().next(); 692 } 693 } 694 } 695 } 696 } 697 698 // Search for the shortest name of chemicals 699 if (pe instanceof SmallMolecule) 700 { 701 String shortName = getShortestName((SmallMolecule) pe); 702 703 if (shortName != null) 704 { 705 if (name == null || (shortName.length() < name.length() && 706 !shortName.isEmpty())) 707 { 708 name = shortName; 709 } 710 } 711 } 712 713 if (name == null || name.trim().isEmpty()) 714 { 715 // Don't leave it without a name 716 name = "noname"; 717 } 718 return name; 719 } 720 721 /** 722 * Searches for the shortest name of the PhysicalEntity. 723 * @param spe entity to search in 724 * @return the shortest name 725 */ 726 private String getShortestName(SimplePhysicalEntity spe) 727 { 728 String name = null; 729 730 for (String s : spe.getName()) 731 { 732 if (name == null || s.length() > name.length()) name = s; 733 } 734 735 EntityReference er = spe.getEntityReference(); 736 737 if (er != null) 738 { 739 for (String s : er.getName()) 740 { 741 if (name == null || s.length() > name.length()) name = s; 742 } 743 } 744 return name; 745 } 746 747 /** 748 * Searches for gene symbol in Xref. 749 * @param xref Xref to search 750 * @return gene symbol 751 */ 752 private String extractGeneSymbol(Xref xref) 753 { 754 if (xref.getDb() != null && ( 755 xref.getDb().equals("HGNC") || 756 xref.getDb().equals("Gene Symbol"))) 757 { 758 String ref = xref.getId(); 759 760 if (ref != null) 761 { 762 ref = ref.trim(); 763 if (ref.contains(":")) ref = ref.substring(ref.indexOf(":") + 1); 764 if (ref.contains("_")) ref = ref.substring(ref.indexOf("_") + 1); 765 766 if (!HGNC.containsSymbol(ref)) 767 { 768 ref = HGNC.getSymbol(ref); 769 } 770 } 771 return ref; 772 } 773 return null; 774 } 775 776 /** 777 * Adds molecule type, and iterates over features of the entity and creates corresponding state 778 * variables. Ignores binding features and covalent-binding features. 779 * 780 * @param pe entity to collect features 781 * @return list of state variables 782 */ 783 private List<Glyph> getInformation(PhysicalEntity pe) 784 { 785 List<Glyph> list = new ArrayList<Glyph>(); 786 787 // Add the molecule type before states if this is a nucleic acid 788 789 if (pe instanceof NucleicAcid) 790 { 791 Glyph g = factory.createGlyph(); 792 g.setClazz(UNIT_OF_INFORMATION.getClazz()); 793 Label label = factory.createLabel(); 794 String s = "mt:"; 795 s += ((pe instanceof Dna || pe instanceof DnaRegion) ? "DNA" : 796 (pe instanceof Rna || pe instanceof RnaRegion) ? "RNA" : "NuclAc"); 797 label.setText(s); 798 g.setLabel(label); 799 list.add(g); 800 } 801 802 // Extract state variables 803 804 extractFeatures(pe.getFeature(), true, list); 805 extractFeatures(pe.getNotFeature(), false, list); 806 807 return list; 808 } 809 810 /** 811 * Converts the features in the given feature set. Adds a "!" in front of NOT features. 812 * 813 * @param features feature set 814 * @param normalFeature specifies the type of features -- normal feature = true, 815 * NOT feature = false 816 * @param list state variables 817 */ 818 private void extractFeatures(Set<EntityFeature> features, boolean normalFeature, 819 List<Glyph> list) 820 { 821 for (EntityFeature feature : features) 822 { 823 if (feature instanceof ModificationFeature || feature instanceof FragmentFeature) 824 { 825 Glyph stvar = factory.createGlyph(); 826 stvar.setClazz(STATE_VARIABLE.getClazz()); 827 828 Glyph.State state = featStrGen.createStateVar(feature, factory); 829 830 if (state != null) 831 { 832 // Add a "!" in front of NOT features 833 834 if (!normalFeature) 835 { 836 state.setValue("!" + state.getValue()); 837 } 838 839 stvar.setState(state); 840 841 list.add(stvar); 842 } 843 } 844 } 845 } 846 847 //-- Section: Create compartments -------------------------------------------------------------| 848 849 /** 850 * Creates or gets the compartment with the given name. 851 * 852 * @param name name of the compartment 853 * @return the compartment glyph 854 */ 855 private Glyph getCompartment(String name) 856 { 857 if (name == null) return null; 858 if (compartmentMap.containsKey(name)) return compartmentMap.get(name); 859 860 Glyph comp = factory.createGlyph(); 861 comp.setId(name); 862 Label label = factory.createLabel(); 863 label.setText(name); 864 comp.setLabel(label); 865 comp.setClazz(COMPARTMENT.getClazz()); 866 867 compartmentMap.put(name, comp); 868 return comp; 869 } 870 871 /** 872 * Gets the compartment of the given PhysicalEntity. 873 * 874 * @param pe PhysicalEntity to look for its compartment 875 * @return name of compartment or null if there is none 876 */ 877 private String getCompartment(PhysicalEntity pe) 878 { 879 CellularLocationVocabulary cl = pe.getCellularLocation(); 880 if (cl != null) 881 { 882 if (!cl.getTerm().isEmpty()) 883 { 884 return cl.getTerm().iterator().next().replaceAll(" ", "_"); 885 } 886 } 887 return null; 888 } 889 890 //-- Section: Create reactions ----------------------------------------------------------------| 891 892 /** 893 * Creates a representation for Conversion. 894 * 895 * @param cnv the conversion 896 * @param direction direction of the conversion to create 897 */ 898 private void createProcessAndConnections(Conversion cnv, 899 ConversionDirectionType direction) 900 { 901 assert cnv.getConversionDirection() == null || 902 cnv.getConversionDirection().equals(direction) || 903 cnv.getConversionDirection().equals(ConversionDirectionType.REVERSIBLE); 904 905 // create the process for the conversion in that direction 906 907 Glyph process = factory.createGlyph(); 908 process.setClazz(PROCESS.getClazz()); 909 process.setId(convertID(cnv.getRDFId()) + direction); 910 glyphMap.put(process.getId(), process); 911 912 // Determine input and output sets 913 914 Set<PhysicalEntity> input = direction.equals(ConversionDirectionType.RIGHT_TO_LEFT) ? 915 cnv.getRight() : cnv.getLeft(); 916 Set<PhysicalEntity> output = direction.equals(ConversionDirectionType.RIGHT_TO_LEFT) ? 917 cnv.getLeft() : cnv.getRight(); 918 919 // Create input and outputs ports for the process 920 addPorts(process); 921 922 Map<PhysicalEntity, Stoichiometry> stoic = getStoichiometry(cnv); 923 924 // Associate inputs to input port 925 926 for (PhysicalEntity pe : input) 927 { 928 Glyph g = getGlyphToLink(pe, process.getId()); 929 createArc(g, process.getPort().get(0), direction == ConversionDirectionType.REVERSIBLE ? 930 PRODUCTION.getClazz() : CONSUMPTION.getClazz(), stoic.get(pe)); 931 } 932 933 // Associate outputs to output port 934 935 for (PhysicalEntity pe : output) 936 { 937 Glyph g = getGlyphToLink(pe, process.getId()); 938 createArc(process.getPort().get(1), g, PRODUCTION.getClazz(), stoic.get(pe)); 939 } 940 941 // Associate controllers 942 943 for (Control ctrl : cnv.getControlledOf()) 944 { 945 // If there is a direction mismatch between the process and the control, just skip it 946 947 if (ctrl instanceof Catalysis) 948 { 949 CatalysisDirectionType catDir = ((Catalysis) ctrl).getCatalysisDirection(); 950 if (catDir != null) 951 { 952 if ((catDir.equals(CatalysisDirectionType.LEFT_TO_RIGHT) && 953 direction.equals(ConversionDirectionType.RIGHT_TO_LEFT)) || 954 (catDir.equals(CatalysisDirectionType.RIGHT_TO_LEFT) && 955 direction.equals(ConversionDirectionType.LEFT_TO_RIGHT))) 956 { 957 // Skip 958 continue; 959 } 960 } 961 } 962 963 Glyph g = createControlStructure(ctrl); 964 if (g != null) createArc(g, process, getControlType(ctrl), null); 965 } 966 967 // Record mapping 968 969 sbgn2BPMap.put(process.getId(), new HashSet<String>()); 970 sbgn2BPMap.get(process.getId()).add(cnv.getRDFId()); 971 } 972 973 /** 974 * Gets the map of stoichiometry coefficients of participants. 975 * @param conv the conversion 976 * @return map from physical entities to their stoichiometry 977 */ 978 private Map<PhysicalEntity, Stoichiometry> getStoichiometry(Conversion conv) 979 { 980 Map<PhysicalEntity, Stoichiometry> map = new HashMap<PhysicalEntity, Stoichiometry>(); 981 for (Stoichiometry stoc : conv.getParticipantStoichiometry()) 982 { 983 map.put(stoc.getPhysicalEntity(), stoc); 984 } 985 return map; 986 } 987 988 /** 989 * Creates a representation for TemplateReaction. 990 * 991 * @param tr template reaction 992 */ 993 private void createProcessAndConnections(TemplateReaction tr) 994 { 995 // create the process for the reaction 996 997 Glyph process = factory.createGlyph(); 998 process.setClazz(PROCESS.getClazz()); 999 process.setId(convertID(tr.getRDFId())); 1000 glyphMap.put(process.getId(), process); 1001 1002 // Add input and output ports 1003 addPorts(process); 1004 1005 // Create a source-and-sink as the input 1006 1007 Glyph sas = factory.createGlyph(); 1008 sas.setClazz(SOURCE_AND_SINK.getClazz()); 1009 sas.setId("SAS_For_" + process.getId()); 1010 glyphMap.put(sas.getId(), sas); 1011 createArc(sas, process.getPort().get(0), CONSUMPTION.getClazz(), null); 1012 1013 // Associate products 1014 1015 for (PhysicalEntity pe : tr.getProduct()) 1016 { 1017 Glyph g = getGlyphToLink(pe, process.getId()); 1018 createArc(process.getPort().get(1), g, PRODUCTION.getClazz(), null); 1019 } 1020 1021 // Associate controllers 1022 1023 for (Control ctrl : tr.getControlledOf()) 1024 { 1025 Glyph g = createControlStructure(ctrl); 1026 if (g != null) createArc(g, process, getControlType(ctrl), null); 1027 } 1028 1029 // Record mapping 1030 1031 sbgn2BPMap.put(process.getId(), new HashSet<String>()); 1032 sbgn2BPMap.get(process.getId()).add(tr.getRDFId()); 1033 } 1034 1035 /** 1036 * Creates or gets the glyph to connect to the control arc. 1037 * 1038 * @param ctrl Control to represent 1039 * @return glyph representing the controller tree 1040 */ 1041 private Glyph createControlStructure(Control ctrl) 1042 { 1043 Glyph cg; 1044 1045 Set<PhysicalEntity> controllers = getControllers(ctrl); 1046 1047 // If no representable controller found, skip this control 1048 if (controllers.isEmpty()) cg = null; 1049 1050 // If there is only one controller with no modulator, put an arc for controller 1051 1052 else if (controllers.size() == 1 && getControllerSize(ctrl.getControlledOf()) == 0) 1053 { 1054 cg = getGlyphToLink(controllers.iterator().next(), convertID(ctrl.getRDFId())); 1055 } 1056 1057 else 1058 { 1059 // This list will contain handles for each participant of the AND structure 1060 List<Glyph> toConnect = new ArrayList<Glyph>(); 1061 1062 // Bundle controllers if necessary 1063 1064 Glyph gg = handlePEGroup(controllers, convertID(ctrl.getRDFId())); 1065 if(gg != null) 1066 toConnect.add(gg); 1067 1068 // Create handles for each controller 1069 1070 for (Control ctrl2 : ctrl.getControlledOf()) 1071 { 1072 Glyph g = createControlStructure(ctrl2); 1073 if (g != null) 1074 { 1075 // If the control is negative, add a NOT in front of it 1076 1077 if (getControlType(ctrl2).equals(INHIBITION.getClazz())) 1078 { 1079 g = addNOT(g); 1080 } 1081 1082 toConnect.add(g); 1083 } 1084 } 1085 1086 // Handle co-factors of catalysis 1087 1088 if (ctrl instanceof Catalysis) 1089 { 1090 Set<PhysicalEntity> cofs = ((Catalysis) ctrl).getCofactor(); 1091 Glyph g = handlePEGroup(cofs, convertID(ctrl.getRDFId())); 1092 if (g != null) 1093 toConnect.add(g); 1094 } 1095 1096 if (toConnect.isEmpty()) 1097 return null; 1098 else if (toConnect.size() == 1) 1099 { 1100 cg = toConnect.iterator().next(); 1101 } 1102 else 1103 { 1104 cg = connectWithAND(toConnect); 1105 } 1106 } 1107 1108 return cg; 1109 } 1110 1111 /** 1112 * Prepares the necessary construct for adding the given PhysicalEntity set to the Control 1113 * being drawn. 1114 * 1115 * @param pes entities to use in control 1116 * @return the glyph to connect to the appropriate place 1117 */ 1118 private Glyph handlePEGroup(Set<PhysicalEntity> pes, String context) 1119 { 1120 int sz = pes.size(); 1121 if (sz > 1) 1122 { 1123 List<Glyph> gs = getGlyphsOfPEs(pes, context); 1124 return connectWithAND(gs); 1125 } 1126 else if (sz == 1 && glyphMap.containsKey(convertID(pes.iterator().next().getRDFId()))) 1127 { 1128 return getGlyphToLink(pes.iterator().next(), context); 1129 } 1130 1131 //'pes' was empty 1132 return null; 1133 } 1134 1135 /** 1136 * Gets the glyphs of the given set of PhysicalEntity objects. Does not create anything. 1137 * 1138 * @param pes entities to get their glyphs 1139 * @return glyphs of entities 1140 */ 1141 private List<Glyph> getGlyphsOfPEs(Set<PhysicalEntity> pes, String context) 1142 { 1143 List<Glyph> gs = new ArrayList<Glyph>(); 1144 for (PhysicalEntity pe : pes) 1145 { 1146 if (glyphMap.containsKey(convertID(pe.getRDFId()))) 1147 { 1148 gs.add(getGlyphToLink(pe, context)); 1149 } 1150 } 1151 return gs; 1152 } 1153 1154 /** 1155 * Creates an AND glyph downstream of the given glyphs. 1156 * 1157 * @param gs upstream glyph list 1158 * @return AND glyph 1159 */ 1160 private Glyph connectWithAND(List<Glyph> gs) 1161 { 1162 // Compose an ID for the AND glyph 1163 1164 String id = ""; 1165 1166 for (Glyph g : gs) 1167 { 1168 id = id + (id.length() > 0 ? "-AND-" : "") + g.getId(); 1169 } 1170 1171 // Create the AND glyph if not exists 1172 1173 Glyph and; 1174 if (!glyphMap.containsKey(id)) 1175 { 1176 and = factory.createGlyph(); 1177 and.setClazz(AND.getClazz()); 1178 and.setId(id); 1179 glyphMap.put(and.getId(), and); 1180 } 1181 else 1182 { 1183 and = glyphMap.get(id); 1184 } 1185 1186 // Connect upstream to the AND glyph 1187 1188 for (Glyph g : gs) 1189 { 1190 createArc(g, and, LOGIC_ARC.getClazz(), null); 1191 } 1192 return and; 1193 } 1194 1195 /** 1196 * Adds a NOT glyph next to the given glyph. 1197 * 1198 * @param g glyph to add NOT 1199 * @return NOT glyph 1200 */ 1201 private Glyph addNOT(Glyph g) 1202 { 1203 // Assemble an ID for the NOT glyph 1204 1205 String id = "NOT-" + g.getId(); 1206 1207 // Find or create the NOT glyph 1208 1209 Glyph not; 1210 if (!glyphMap.containsKey(id)) 1211 { 1212 not = factory.createGlyph(); 1213 not.setId(id); 1214 not.setClazz(NOT.getClazz()); 1215 glyphMap.put(not.getId(), not); 1216 } 1217 else 1218 { 1219 not = glyphMap.get(id); 1220 } 1221 1222 // Connect the glyph and NOT 1223 createArc(g, not, LOGIC_ARC.getClazz(), null); 1224 1225 return not; 1226 } 1227 1228 /** 1229 * Converts the control type of the Control to the SBGN classes. 1230 * 1231 * @param ctrl Control to get its type 1232 * @return SBGN type of the Control 1233 */ 1234 private String getControlType(Control ctrl) 1235 { 1236 if (ctrl instanceof Catalysis) 1237 { 1238 // Catalysis has its own class 1239 return CATALYSIS.getClazz(); 1240 } 1241 1242 ControlType type = ctrl.getControlType(); 1243 if (type == null) 1244 { 1245 // Use stimulation as the default control type 1246 return STIMULATION.getClazz(); 1247 } 1248 1249 // Map control type to stimulation or inhibition 1250 1251 switch (type) 1252 { 1253 case ACTIVATION: 1254 case ACTIVATION_ALLOSTERIC: 1255 case ACTIVATION_NONALLOSTERIC: 1256 case ACTIVATION_UNKMECH: return STIMULATION.getClazz(); 1257 case INHIBITION: 1258 case INHIBITION_ALLOSTERIC: 1259 case INHIBITION_OTHER: 1260 case INHIBITION_UNKMECH: 1261 case INHIBITION_COMPETITIVE: 1262 case INHIBITION_IRREVERSIBLE: 1263 case INHIBITION_UNCOMPETITIVE: 1264 case INHIBITION_NONCOMPETITIVE: return INHIBITION.getClazz(); 1265 } 1266 throw new RuntimeException("Invalid control type: " + type); 1267 } 1268 1269 /** 1270 * Gets the size of representable Controller of this set of Controls. 1271 * 1272 * @param ctrlSet Controls to check their controllers 1273 * @return size of representable controllers 1274 */ 1275 private int getControllerSize(Set<Control> ctrlSet) 1276 { 1277 int size = 0; 1278 for (Control ctrl : ctrlSet) 1279 { 1280 size += getControllers(ctrl).size(); 1281 } 1282 return size; 1283 } 1284 1285 /** 1286 * Gets the size of representable Controller of this Control. 1287 * 1288 * @param ctrl Control to check its controllers 1289 * @return size of representable controllers 1290 */ 1291 private Set<PhysicalEntity> getControllers(Control ctrl) 1292 { 1293 Set<PhysicalEntity> controllers = new HashSet<PhysicalEntity>(); 1294 for (Controller clr : ctrl.getController()) 1295 { 1296 if (clr instanceof PhysicalEntity && glyphMap.containsKey(convertID(clr.getRDFId()))) 1297 { 1298 controllers.add((PhysicalEntity) clr); 1299 } 1300 } 1301 return controllers; 1302 } 1303 1304 1305 /** 1306 * Adds input and output ports to the glyph. 1307 * 1308 * @param g glyph to add ports 1309 */ 1310 private void addPorts(Glyph g) 1311 { 1312 Port inputPort = factory.createPort(); 1313 Port outputPort = factory.createPort(); 1314 inputPort.setId(g.getId() + ".input"); 1315 outputPort.setId(g.getId() + ".output"); 1316 g.getPort().add(inputPort); 1317 g.getPort().add(outputPort); 1318 } 1319 1320 //-- Section: Create arcs ---------------------------------------------------------------------| 1321 1322 /** 1323 * Creates an arc from the source to the target, and sets its class to the specified clazz. 1324 * Puts the new arc in the sullied arcMap. 1325 * 1326 * @param source source of the arc -- either Glyph or Port 1327 * @param target target of the arc -- either Glyph or Port 1328 * @param clazz class of the arc 1329 */ 1330 private void createArc(Object source, Object target, String clazz, Stoichiometry stoic) 1331 { 1332 assert source instanceof Glyph || source instanceof Port : "source = " + source; 1333 assert target instanceof Glyph || target instanceof Port : "target = " + target; 1334 1335 Arc arc = factory.createArc(); 1336 arc.setSource(source); 1337 arc.setTarget(target); 1338 arc.setClazz(clazz); 1339 1340 String sourceID = source instanceof Glyph ? 1341 ((Glyph) source).getId() : ((Port) source).getId(); 1342 String targetID = target instanceof Glyph ? 1343 ((Glyph) target).getId() : ((Port) target).getId(); 1344 1345 arc.setId(sourceID + "--to--" + targetID); 1346 1347 if (stoic != null && stoic.getStoichiometricCoefficient() != 1F) 1348 { 1349 Glyph card = factory.createGlyph(); 1350 card.setClazz(CARDINALITY.getClazz()); 1351 Label label = factory.createLabel(); 1352 label.setText(new DecimalFormat("0.##").format(stoic.getStoichiometricCoefficient())); 1353 card.setLabel(label); 1354 arc.getGlyph().add(card); 1355 } 1356 1357 Arc.Start start = new Arc.Start(); 1358 start.setX(0); 1359 start.setY(0); 1360 arc.setStart(start); 1361 1362 Arc.End end = new Arc.End(); 1363 end.setX(0); 1364 end.setY(0); 1365 arc.setEnd(end); 1366 1367 arcMap.put(arc.getId(), arc); 1368 } 1369 1370 /** 1371 * Collects root-level glyphs in the given glyph collection. 1372 * 1373 * @param glyphCol glyph collection to search 1374 * @return set of roots 1375 */ 1376 private Set<Glyph> getRootGlyphs(Collection<Glyph> glyphCol) 1377 { 1378 Set<Glyph> root = new HashSet<Glyph>(glyphCol); 1379 Set<Glyph> children = new HashSet<Glyph>(); 1380 1381 for (Glyph glyph : glyphCol) 1382 { 1383 addChildren(glyph, children); 1384 } 1385 root.removeAll(children); 1386 return root; 1387 } 1388 1389 /** 1390 * Adds children of this glyph to the specified set recursively. 1391 * @param glyph to collect children 1392 * @param set to add 1393 */ 1394 private void addChildren(Glyph glyph, Set<Glyph> set) 1395 { 1396 for (Glyph child : glyph.getGlyph()) 1397 { 1398 set.add(child); 1399 addChildren(child, set); 1400 } 1401 } 1402 1403 /** 1404 * Gets the mapping from SBGN IDs to BioPAX IDs. This mapping is currently one-to-many, but has 1405 * potential to become many-to-many in the future. 1406 * @return sbgn-to-biopax mapping 1407 */ 1408 public Map<String, Set<String>> getSbgn2BPMap() 1409 { 1410 return sbgn2BPMap; 1411 } 1412 1413 private String convertID(String biopaxID) 1414 { 1415 return biopaxID.replaceAll("[^-\\w]", "_"); 1416 } 1417 1418 //-- Section: Static initialization -----------------------------------------------------------| 1419 1420 /** 1421 * Initializes resources. 1422 */ 1423 static 1424 { 1425 factory = new ObjectFactory(); 1426 1427 typeMatchMap = new HashMap<Class<? extends BioPAXElement>, String>(); 1428 typeMatchMap.put(Protein.class, MACROMOLECULE.getClazz()); 1429 typeMatchMap.put(SmallMolecule.class, SIMPLE_CHEMICAL.getClazz()); 1430 typeMatchMap.put(Dna.class, NUCLEIC_ACID_FEATURE.getClazz()); 1431 typeMatchMap.put(Rna.class, NUCLEIC_ACID_FEATURE.getClazz()); 1432 typeMatchMap.put(DnaRegion.class, NUCLEIC_ACID_FEATURE.getClazz()); 1433 typeMatchMap.put(RnaRegion.class, NUCLEIC_ACID_FEATURE.getClazz()); 1434 typeMatchMap.put(NucleicAcid.class, NUCLEIC_ACID_FEATURE.getClazz()); 1435 typeMatchMap.put(PhysicalEntity.class, UNSPECIFIED_ENTITY.getClazz()); 1436 typeMatchMap.put(SimplePhysicalEntity.class, UNSPECIFIED_ENTITY.getClazz()); 1437 typeMatchMap.put(Complex.class, COMPLEX.getClazz()); 1438 } 1439}