001package org.biopax.paxtools.io; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.biopax.paxtools.controller.*; 006import org.biopax.paxtools.model.BioPAXElement; 007import org.biopax.paxtools.model.BioPAXFactory; 008import org.biopax.paxtools.model.BioPAXLevel; 009import org.biopax.paxtools.model.Model; 010import org.biopax.paxtools.model.level2.deltaGprimeO; 011import org.biopax.paxtools.model.level2.kPrime; 012import org.biopax.paxtools.model.level2.physicalEntityParticipant; 013import org.biopax.paxtools.util.BioPaxIOException; 014import org.biopax.paxtools.util.IllegalBioPAXArgumentException; 015 016import java.io.*; 017import java.util.Map; 018 019 020/** 021 * 022 */ 023public abstract class BioPAXIOHandlerAdapter implements BioPAXIOHandler 024{ 025 private boolean treatNilAsNull; 026 027 private boolean convertingFromLevel1ToLevel2 = false; 028 029 private boolean fixReusedPEPs = false; 030 031 private static final Log log = LogFactory.getLog(BioPAXIOHandlerAdapter.class); 032 033 protected BioPAXLevel level; 034 035 protected BioPAXFactory factory; 036 037 protected EditorMap editorMap; 038 039 protected Map<String, String> namespaces; 040 041 protected static final String rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; 042 043 protected static final String rdfs = "http://www.w3.org/2000/01/rdf-schema#"; 044 045 protected String bp; //current BioPAX namespace prefix value 046 047 protected static final String xsd = "http://www.w3.org/2001/XMLSchema#"; 048 049 protected static final String owl = "owl=http://www.w3.org/2002/07/owl#"; 050 051 protected String base; 052 053 public BioPAXIOHandlerAdapter() 054 { 055 this(null, null); 056 } 057 058 public BioPAXIOHandlerAdapter(BioPAXLevel level) 059 { 060 this(null, level); 061 } 062 063 public BioPAXIOHandlerAdapter(BioPAXFactory factory, BioPAXLevel level) 064 { 065 resetLevel(level, factory); 066 } 067 068 /** 069 * Updates the level and factory for this I/O 070 * (final - because used in the constructor) 071 * @param level BioPAX Level 072 * @param factory concrete BioPAX factory impl. 073 */ 074 protected final void resetLevel(BioPAXLevel level, BioPAXFactory factory) 075 { 076 this.level = (level != null) ? level : BioPAXLevel.L3; 077 this.factory = (factory != null) ? factory : this.level.getDefaultFactory(); 078 079 // default flags 080 if (this.level == BioPAXLevel.L1) 081 { 082 this.convertingFromLevel1ToLevel2 = true; 083 this.fixReusedPEPs = true; 084 } else if (this.level == BioPAXLevel.L2) 085 { 086 this.fixReusedPEPs = true; 087 } 088 089 bp = this.level.getNameSpace(); 090 091 resetEditorMap(); //implemented by concrete subclasses 092 } 093 094 /** 095 * Updates the member EditorMap for the new BioPAX level and factory (different implementations of 096 * EditorMap can be used in modules, e.g. SimpleEditorMap and JenaEditorMap.) 097 */ 098 protected abstract void resetEditorMap(); 099 100 /** 101 * According the BioPAX documentation, it is illegal to reuse a Physical Entity Participant (PEP). 102 * If this value is set to <em>true</em> (default value), a reused PEP will be duplicated while 103 * converting the OWL file into a model. 104 * @see org.biopax.paxtools.controller.ReusedPEPHelper 105 */ 106 107 private ReusedPEPHelper reusedPEPHelper; 108 109 /** 110 * Enables (true) or disables (false) the fixing of reused peps. 111 * @param fixReusedPEPs true if fixing is desired 112 * @see #fixReusedPEPs 113 */ 114 public void fixReusedPEPs(boolean fixReusedPEPs) 115 { 116 this.fixReusedPEPs = fixReusedPEPs; 117 } 118 119 /** 120 * A workaround for a common issue that was present in BioCyc exports. For non-present values BioCyc exports 121 * contained the string "NIL". 122 * @param treatNILasNull true/false 123 * @deprecated This problem is fixed and this option is no longer needed for recent BioCyc exports. Enable only if 124 * you are parsing a legacy export. 125 */ 126 public void treatNilAsNull(boolean treatNILasNull) 127 { 128 this.treatNilAsNull = treatNILasNull; 129 } 130 131 /** 132 * This method enables silent conversion of Level1 to Level2. If disabled the method will throw an error. 133 * @param convertingFromLevel1ToLevel2 true/false 134 * @deprecated BioPAX Level 1 exports are extremely rare and obsolete. 135 */ 136 public void setConvertingFromLevel1ToLevel2(boolean convertingFromLevel1ToLevel2) 137 { 138 this.convertingFromLevel1ToLevel2 = convertingFromLevel1ToLevel2; 139 } 140 141 /** 142 * @return true if this reader is treating string 'NIL' as Null 143 * @deprecated This problem is fixed and this option is no longer needed for recent BioCyc exports. Enable only if 144 * you are parsing a legacy export. 145 */ 146 public boolean isTreatNilAsNull() 147 { 148 return treatNilAsNull; 149 } 150 151 /** 152 * @return true is converting Level1 to Level2 silently. 153 */ 154 public boolean isConvertingFromLevel1ToLevel2() 155 { 156 return convertingFromLevel1ToLevel2; 157 } 158 159 /** 160 * Workaround for a very common Level 2 issue. Most level2 exports in the past reused PhysicalEntityParticipants 161 * as if they represent states. This is problematic because PEPs also contain stoichiometry information which is 162 * specific to a reaction. BioPAX spec says that PEPs should not be reused across reactions. 163 * 164 * As Level2 exports getting obsolete this method is slated for deprecation. 165 * @return true if this Handler automatically splits reused PEPs to interaction specific PEPS. 166 */ 167 public boolean isFixReusedPEPs() 168 { 169 return fixReusedPEPs; 170 } 171 172 /** 173 * This is a helper class initialized only if fixReusedPEPs is true. 174 * @return helper object 175 */ 176 protected ReusedPEPHelper getReusedPEPHelper() 177 { 178 return reusedPEPHelper; 179 } 180 181 182 public BioPAXFactory getFactory() 183 { 184 return factory; 185 } 186 187 188 public void setFactory(BioPAXFactory factory) 189 { 190 this.factory = factory; 191 } 192 193 194 public EditorMap getEditorMap() 195 { 196 return editorMap; 197 } 198 199 public void setEditorMap(EditorMap editorMap) 200 { 201 this.editorMap = editorMap; 202 } 203 204 public BioPAXLevel getLevel() 205 { 206 return level; 207 } 208 209 /** 210 * This method reads multiple files and returns a merged model. 211 * @deprecated experimental and incomplete; e.g., files are to be ordered, i.e., 212 * a former file should not point to the latter. Use it at your own risk. 213 * Or, use #convertFromOWL to get independent models; then, try merging them... 214 * @param files Dependency ordered biopax owl file names 215 * @return a merged model. 216 * @throws java.io.FileNotFoundException when any file can not be found 217 */ 218 @Deprecated 219 public Model convertFromMultipleOwlFiles(String... files) throws FileNotFoundException 220 { 221 Model model = this.factory.createModel(); 222 223 for (String file : files) 224 { 225 FileInputStream in = new FileInputStream(new File(file)); 226 if (log.isDebugEnabled()) 227 { 228 log.debug("start reading file:" + file); 229 } 230// createAndBind(in, model); //TODO why this line is currently commented out; since when?.. 231 232 if (log.isDebugEnabled()) 233 { 234 log.debug("read file: " + file); 235 } 236 } 237 return model; 238 } 239 240 241 /** 242 * Reads a BioPAX model from an OWL file input stream (<em>in</em>) and converts it to a model. 243 * @param in inputStream from which the model will be read 244 * @return an empty model in case of invalid input. 245 */ 246 public Model convertFromOWL(InputStream in) 247 { 248 init(in); 249 250 //cache the namespaces. 251 namespaces = this.readNameSpaces(); 252 253 autodetectBiopaxLevel(); // this may update level, editorMap and factory! 254 255// bp = level.getNameSpace(); 256 257 Model model = factory.createModel(); 258 259 model.getNameSpacePrefixMap().putAll(namespaces); 260 261 model.setXmlBase(base); 262 263 boolean fixingPEPS = model.getLevel() == BioPAXLevel.L2 && this.isFixReusedPEPs(); 264 if (fixingPEPS) 265 { 266 reusedPEPHelper = new ReusedPEPHelper(model); 267 } 268 269 createAndBind(model); 270 271 if (fixingPEPS) 272 { 273 this.getReusedPEPHelper().copyPEPFields(); 274 } 275 276 reset(in); 277 278 return model; 279 } 280 281 protected void reset(InputStream in) 282 { 283 try 284 { 285 in.close(); 286 287 } 288 catch (IOException e) 289 { 290 throw new RuntimeException("Failed to close the file"); 291 } 292 293 } 294 295 private void autodetectBiopaxLevel() 296 { 297 BioPAXLevel filelevel = null; 298 for (String namespaceValue : namespaces.values()) 299 { 300 filelevel = BioPAXLevel.getLevelFromNameSpace(namespaceValue); 301 if (filelevel != null) 302 { 303 if (log.isDebugEnabled()) 304 log.debug("Auto-detected biopax " + filelevel + " (current settings are for Level " + level + ")"); 305 break; 306 } 307 } 308 309 if (filelevel == null) 310 { 311 log.error("Cannot detect biopax level."); 312 throw new BioPaxIOException("Cannot detect biopax level."); 313 } else if (level != filelevel) 314 { 315 if (log.isDebugEnabled()) log.debug("Reset to the default factory for the detected BioPAX level."); 316 resetLevel(filelevel, filelevel.getDefaultFactory()); 317 } 318 } 319 320 /** 321 * This method is called by the reader for each OWL instance in the OWL model. It creates a POJO instance, with 322 * the given id and inserts it into the model. The inserted object is "clean" in the sense that its properties are 323 * not set yet. 324 * 325 * Implementers of this abstract class can override this method to inject code during object creation. 326 * @param model to be inserted 327 * @param id of the new object. The model should not contain another object with the same ID. 328 * @param localName of the class to be instantiated. 329 */ 330 protected void createAndAdd(Model model, String id, String localName) 331 { 332 BioPAXElement bpe = this.getFactory().create(localName, id); 333 334 if (log.isTraceEnabled()) 335 { 336 log.trace("id:" + id + " " + localName + " : " + bpe); 337 } 338 /* null might occur here, 339 * so the following is to prevent the NullPointerException 340 * and to continue the model assembling. 341 */ 342 if (bpe != null) 343 { 344 model.add(bpe); 345 } else 346 { 347 log.warn("null object created during reading. It might not be an official BioPAX class.ID: " + id + 348 " Class " + 349 "name " + localName); 350 } 351 } 352 353 /** 354 * This method provides a hook for the implementers of this abstract class to perform the initial reading from the 355 * input stream. 356 * 357 * @param in BioPAX RDF/XML input stream 358 */ 359 protected abstract void init(InputStream in); 360 361 /** 362 * This method provides a hook for the implementers of this abstract class to set the namespaces of the model. 363 * @return a map of namespaces. 364 */ 365 protected abstract Map<String, String> readNameSpaces(); 366 367 /** 368 * This method provides a hook for the implementers of this abstract class to create objects themselves and bind 369 * the properties to the objects. 370 * @param model to be populated 371 */ 372 protected abstract void createAndBind(Model model); 373 374 /** 375 * Several workarounds for two properties, DeltaG and KEQ, that changed from Level1 for Level2 376 * @param editor that is responsible for the property that is currently being assigned to the bpe. This method 377 * will only react if the editor is one of Delta-G or KEQ. These two properties have integer values in L1 but were 378 * upgraded to 5-tuples called DeltaGPrime0 and KPrime in L2. 379 * @param bpe that is being bound. 380 * @param model that is being populated. 381 * @param value of the property which is an integer. 382 * @return a modified BioPAX L2 element, DeltaGPrime0 or Kprime if the editor is Delta-G or KEQ respectively. 383 * Null otherwise. 384 * @deprecated BioPAX Level 1 exports are extremely rare and obsolete. 385 */ 386 protected BioPAXElement L1ToL2Fixes(PropertyEditor editor, BioPAXElement bpe, Model model, String value) 387 { 388 BioPAXElement created = null; 389 390 if (this.isConvertingFromLevel1ToLevel2()) 391 { 392 if (editor.getProperty().equals("DELTA-G")) 393 { 394 deltaGprimeO aDeltaGprime0 = model.addNew(deltaGprimeO.class, (bpe.getRDFId() + "-DELTA-G")); 395 aDeltaGprime0.setDELTA_G_PRIME_O(Float.valueOf(value)); 396 created = aDeltaGprime0; 397 } 398 if (editor.getProperty().equals("KEQ")) 399 { 400 kPrime aKPrime = model.addNew(kPrime.class, (bpe.getRDFId() + "-KEQ")); 401 aKPrime.setK_PRIME(Float.valueOf(value)); 402 created = aKPrime; 403 } 404 405 } 406 407 return created; 408 } 409 410 /** 411 * This method currently only fixes reusedPEPs if the option is set. As L2 is becoming obsolete this method will be 412 * slated for deprecation. 413 * @param bpe to be bound 414 * @param value to be assigned. 415 * @return a "fixed" value. 416 */ 417 protected Object resourceFixes(BioPAXElement bpe, Object value) 418 { 419 if (this.isFixReusedPEPs() && value instanceof physicalEntityParticipant) 420 { 421 value = this.getReusedPEPHelper().fixReusedPEP((physicalEntityParticipant) value, bpe); 422 } 423 return value; 424 } 425 426 /** 427 * This method binds the value to the bpe. Actual assignment is handled by the editor - but this method performs 428 * most of the workarounds and also error handling due to invalid parameters. 429 * @param valueString to be assigned 430 * @param editor that maps to the property 431 * @param bpe to be bound 432 * @param model to be populated. 433 */ 434 protected void bindValue(String valueString, PropertyEditor editor, BioPAXElement bpe, Model model) 435 { 436 437 if (log.isDebugEnabled()) 438 { 439 log.debug("Binding: " + bpe + '(' + bpe.getModelInterface() + " has " + editor + ' ' + valueString); 440 } 441 Object value = valueString; 442 443 if (editor instanceof ObjectPropertyEditor) 444 { 445 value = model.getByID(valueString); 446 value = resourceFixes(bpe, value); 447 if (value == null) 448 { 449 value = L1ToL2Fixes(editor, bpe, model, valueString); 450 if (value == null) 451 { 452 throw new IllegalBioPAXArgumentException( 453 "Illegal or Dangling Value/Reference: " + valueString + " (element: " + bpe.getRDFId() + 454 " property: " + editor.getProperty() + ")"); 455 } 456 } else if (this.isTreatNilAsNull() && valueString.trim().equalsIgnoreCase("NIL")) 457 { 458 value = null; 459 } 460 } 461 if (editor == null) 462 { 463 log.error("Editor is null. This probably means an invalid BioPAX property. Failed to set " + valueString); 464 } else 465 { 466 editor.setValueToBean(value, bpe); 467 } 468 } 469 470 /** 471 * Paxtools maps BioPAX:comment (L3) and BioPAX:COMMENT (L2) to rdf:comment. This method handles that. 472 * @param bpe to be bound. 473 * @return a property editor responsible for editing comments. 474 */ 475 protected StringPropertyEditor getRDFCommentEditor(BioPAXElement bpe) 476 { 477 StringPropertyEditor editor; 478 Class<? extends BioPAXElement> inter = bpe.getModelInterface(); 479 if (this.getLevel().equals(BioPAXLevel.L3)) 480 { 481 editor = (StringPropertyEditor) this.getEditorMap().getEditorForProperty("comment", inter); 482 } else 483 { 484 editor = (StringPropertyEditor) this.getEditorMap().getEditorForProperty("COMMENT", inter); 485 } 486 return editor; 487 } 488 489 /** 490 * Similar to {@link BioPAXIOHandler#convertToOWL(org.biopax.paxtools.model.Model, 491 * java.io.OutputStream)} (org.biopax.paxtools.model.Model, Object)}, but 492 * extracts a sub-model, converts it into BioPAX (OWL) format, 493 * and writes it into the outputStream. 494 * Saved data can be then read via {@link BioPAXIOHandler} 495 * interface (e.g., {@link SimpleIOHandler}). 496 * @param model model to be converted into OWL format 497 * @param outputStream output stream into which the output will be written 498 * @param ids optional list of "root" element absolute URIs (all direct/indirect child objects are auto-exported as well) 499 */ 500 public void convertToOWL(Model model, OutputStream outputStream, String... ids) 501 { 502 if (ids.length == 0) 503 { 504 convertToOWL(model, outputStream); 505 } else 506 { 507 Model m = model.getLevel().getDefaultFactory().createModel(); 508 m.setXmlBase(model.getXmlBase()); 509 510 //to avoid 'nextStep' that may lead to infinite loops - 511 Fetcher fetcher = new Fetcher(SimpleEditorMap.get(model.getLevel()), 512 Fetcher.nextStepFilter); 513 514 for (String uri : ids) 515 { 516 BioPAXElement bpe = model.getByID(uri); 517 if (bpe != null) 518 { 519 fetcher.fetch(bpe, m); 520 } 521 } 522 523 convertToOWL(m, outputStream); 524 } 525 } 526}