001package org.biopax.paxtools.io;
002
003import org.apache.commons.lang.StringEscapeUtils;
004import org.apache.commons.logging.Log;
005import org.apache.commons.logging.LogFactory;
006import org.biopax.paxtools.controller.AbstractPropertyEditor;
007import org.biopax.paxtools.controller.PropertyEditor;
008import org.biopax.paxtools.controller.SimpleEditorMap;
009import org.biopax.paxtools.model.BioPAXElement;
010import org.biopax.paxtools.model.BioPAXFactory;
011import org.biopax.paxtools.model.BioPAXLevel;
012import org.biopax.paxtools.model.Model;
013import org.biopax.paxtools.model.level3.BiochemicalPathwayStep;
014import org.biopax.paxtools.model.level3.Named;
015import org.biopax.paxtools.util.BioPaxIOException;
016import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
017
018import javax.xml.stream.XMLInputFactory;
019import javax.xml.stream.XMLStreamException;
020import javax.xml.stream.XMLStreamReader;
021import java.io.*;
022import java.util.*;
023
024import static javax.xml.stream.XMLStreamConstants.*;
025
026/**
027 * Simple BioPAX reader/writer.
028 *
029 * This class provides a JAXP based I/O handler. As compared to Jena based implementation it offers ~10x performance,
030 * significantly less memory requirements when reading large files and a lightweight deployement. It, however,
031 * is not as robust as the Jena based reader and can not read non-RDF/XML OWL formats or non-UTF encodings.For those,
032 * you might want to use the JenaIOHandler class
033 */
034public final class SimpleIOHandler extends BioPAXIOHandlerAdapter
035{
036        private static final Log log = LogFactory.getLog(SimpleIOHandler.class);
037
038        private XMLStreamReader r;
039
040        boolean propertyContext = false;
041
042        private List<Triple> triples;
043
044        private boolean mergeDuplicates;
045
046        private static final String owlNS = "http://www.w3.org/2002/07/owl#";
047
048        private static final String xsdNS = "http://www.w3.org/2001/XMLSchema#";
049
050        private static final String rdfNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
051
052        private static final String RDF_ID = "rdf:ID=\"";
053
054        private static final String RDF_about = "rdf:about=\"";
055
056        private static final String newline = System.getProperty("line.separator");
057
058        private static final String close = "\">";
059
060        private boolean normalizeNameSpaces;
061
062        private boolean absoluteUris;
063
064
065        // --------------------------- CONSTRUCTORS ---------------------------
066
067        /**
068         * Basic constructor, defaults to Level 3 and default BioPAXFactory
069         */
070        public SimpleIOHandler()
071        {
072                this(null, null);
073        }
074
075        /**
076         * Basic constructor, defaults to level.defaultFactory
077         * @param level BioPAXLevel to handle.
078         */
079        public SimpleIOHandler(BioPAXLevel level)
080        {
081                this(level.getDefaultFactory(), level);
082        }
083
084        /**
085         * Full constructor
086         * @param factory to create BioPAX objects
087         * @param level BioPAX level to handle.
088         */
089        public SimpleIOHandler(BioPAXFactory factory, BioPAXLevel level)
090        {
091                super(factory, level);
092                normalizeNameSpaces = true;
093                mergeDuplicates = false;
094        }
095
096        /**
097         * If set to true, the reader will merge duplicate individuals, i.e. individuals that has the same ID. Otherwise
098         * it will throw an exception. By default it is set to false.
099         * @param mergeDuplicates true/false
100         */
101        public void mergeDuplicates(boolean mergeDuplicates)
102        {
103                this.mergeDuplicates = mergeDuplicates;
104        }
105
106        /**
107         * If set to true, property editors will check restrictions at the subclass level and throw an exception if
108         * violated. This is true by default.
109         *
110         * WARNING: This is a static parameter and will change behaviour for the whole thread. Do not change unless you
111         * need to read a (relatively exotic) BioPAX export that violates subclass level restrictions.
112         * @param checkRestrictions true/false
113         */
114        public void checkRestrictions(boolean checkRestrictions)
115        {
116                AbstractPropertyEditor.checkRestrictions.set(checkRestrictions);
117        }
118        // -------------------------- OTHER METHODS --------------------------
119
120        /**
121         * This method resets the editor map. Editor maps are used to map property names to property editors. Resetting the
122         * editor map might be required to switch between levels.
123         */
124        @Override
125        protected void resetEditorMap()
126        {
127                setEditorMap(SimpleEditorMap.get(this.getLevel())); // was 'level' - bug!
128        }
129
130        /**
131         * This may be required for external applications to access the specific information (e.g.,
132         * location) when reporting XML exceptions.
133         * @return current XML stream state summary
134         */
135        public String getXmlStreamInfo()
136        {
137                StringBuilder sb = new StringBuilder();
138
139                int event = r.getEventType();
140                if (event == START_ELEMENT || event == END_ELEMENT || event == ENTITY_REFERENCE)
141                {
142                        sb.append(r.getLocalName());
143                }
144
145                if (r.getLocation() != null)
146                {
147                        sb.append(" line ");
148                        sb.append(r.getLocation().getLineNumber());
149                        sb.append(" column ");
150                        sb.append(r.getLocation().getColumnNumber());
151                }
152
153                return sb.toString();
154        }
155
156
157        @Override protected void init(InputStream in)
158        {
159                try
160                {
161                        XMLInputFactory xmlf = XMLInputFactory.newInstance();
162                        //this is to return string with encoded chars as one event (not splitting)
163                        xmlf.setProperty("javax.xml.stream.isCoalescing", true);
164                        r = xmlf.createXMLStreamReader(in);
165                        
166                        triples = new LinkedList<Triple>();
167
168                }
169                catch (XMLStreamException e)
170                {
171                        //e.printStackTrace();
172                        throw new BioPaxIOException(e.getClass().getSimpleName() + " " + e.getMessage() + "; " + e.getLocation());
173                }
174
175        }
176
177        @Override protected void reset(InputStream in)
178        {
179
180                this.triples=null;
181                try
182                {
183                        r.close();
184                }
185                catch (XMLStreamException e)
186                {
187                        throw new RuntimeException("Can't close the stream");
188                }
189                r=null;
190                super.reset(in);
191        }
192
193        @Override protected Map<String, String> readNameSpaces()
194        {
195                Map<String, String> ns = new HashMap<String, String>();
196                try
197                {
198                        if (r.getEventType() == START_DOCUMENT)
199                        {
200                                r.next();
201                        } else
202                        {
203                                throw new BioPaxIOException("Unexpected element at start");
204                        }
205
206                        // Skip any comment before we read the RDF headers
207                        while (r.getEventType() == COMMENT)
208                        {
209                                r.next();
210                        }
211
212                        if (r.getEventType() != START_ELEMENT)
213                        {
214                                throw new BioPaxIOException("Unexpected element at start: " + r.getEventType());
215                        }
216
217                        if (!r.getLocalName().equalsIgnoreCase("rdf"))
218                        {
219                                throw new BioPaxIOException("Unexpected element at start: " + r.getLocalName());
220                        }
221
222                        int count = r.getNamespaceCount();
223                        for (int i = 0; i < count; i++)
224                        {
225                                String pre = r.getNamespacePrefix(i);
226                                if (pre == null)
227                                {
228                                        pre = "";
229                                }
230                                String namespace = r.getNamespaceURI(pre);
231
232                                ns.put(pre, namespace);
233                        }
234
235                        base = null;
236                        for (int i = 0; i < r.getAttributeCount(); i++)
237                        {
238                                if ("base".equalsIgnoreCase(r.getAttributeLocalName(i)) &&
239                                    "xml".equalsIgnoreCase(r.getAttributePrefix(i)))
240                                {
241                                        base = r.getAttributeValue(i);
242                                }
243                        }
244                }
245                catch (XMLStreamException e)
246                {
247                        throw new BioPaxIOException(e.getClass().getSimpleName() + " " + e.getMessage() + "; " + e.getLocation());
248                }
249                return ns;
250
251        }
252
253        @Override protected void createAndBind(Model model)
254        {
255                try
256                {
257                        int type;
258                        while ((type = r.getEventType()) != END_DOCUMENT)
259                        {
260                                switch (type)
261                                {
262                                        case START_ELEMENT:
263                                                if (BioPAXLevel.isInBioPAXNameSpace(r.getName().getNamespaceURI()))
264                                                {
265                                                        String lname = r.getLocalName();
266                                                        BioPAXLevel level = getLevel();
267                                                        Class<? extends BioPAXElement> clazz;
268                                                        try
269                                                        {
270                                                                clazz = level.getInterfaceForName(lname);
271                                                        }
272                                                        catch (IllegalBioPAXArgumentException e)
273                                                        {
274                                                                log.fatal("Unknown BioPAX element location " + lname + " at " + r.getLocation());
275                                                                throw e; // for backward compatibility
276                                                        }
277                                                        if (this.getFactory().canInstantiate(clazz))
278                                                        {
279                                                                processIndividual(model);
280                                                        } else
281                                                        {
282                                                                if (log.isTraceEnabled())
283                                                                {
284                                                                        log.trace("Ignoring element: " + lname);
285                                                                }
286                                                                skip();
287                                                        }
288
289                                                }
290                                                break;
291                                        case CHARACTERS:
292                                                if (log.isTraceEnabled())
293                                                {
294                                                        StringBuilder sb = new StringBuilder("Ignoring text ");
295                                                        if (r.hasName()) sb.append(r.getLocalName());
296                                                        if (r.hasText()) sb.append(r.getText());
297                                                        log.trace(sb.toString());
298                                                }
299                                                break;
300                                        case END_ELEMENT:
301                                                if (log.isTraceEnabled())
302                                                {
303                                                        log.trace(r);
304                                                }
305                                                break;
306                                        default:
307                                                if (log.isTraceEnabled())
308                                                {
309                                                        log.trace("Test this!:" + type);
310                                                }  //TODO
311                                }
312                                r.next();
313
314                        }
315                r.close();
316                }
317                catch (XMLStreamException e)
318                {
319                        throw new BioPaxIOException(e.getClass().getSimpleName() + " " + e.getMessage() + "; " + e.getLocation());
320                }
321
322                for (Triple triple : triples)
323                {
324                        try
325                        {
326                                bindValue(triple, model);
327                        }
328                        catch (IllegalBioPAXArgumentException e)
329                        {
330                                log.warn("Binding " + e);
331                        }
332                }
333
334        }
335
336        /**
337         * Binds property.
338         *
339         * This method also throws exceptions related to binding.
340         * @param triple A java object that represents an RDF Triple - domain-property-range.
341         * @param model that is being populated.
342         */
343        private void bindValue(Triple triple, Model model)
344        {
345                if (log.isDebugEnabled())
346                {
347                        log.debug(triple);
348                }
349                BioPAXElement domain = model.getByID(triple.domain);
350
351                PropertyEditor editor = this.getEditorMap().getEditorForProperty(triple.property, domain.getModelInterface());
352
353                bindValue(triple.range, editor, domain, model);
354        }
355
356
357        private String processIndividual(Model model) throws XMLStreamException
358        {
359                String s = r.getLocalName();
360                String id;
361                try
362                {
363                        id = getId();
364                }
365                catch (NullPointerException e)
366                {
367                        throw new BioPaxIOException("Error processing individual " + s + ". rdf:ID or rdf:about not found!", e);
368                }
369
370                if (factory.canInstantiate((level.getInterfaceForName(s))))
371                {
372                        BioPAXElement bpe = model.getByID(id);
373                        if (!mergeDuplicates || bpe == null)
374                        {
375                                createBpe(s, id, model); //throws an exception when (mergeDuplicates==false && bpe!=null)
376                        } else if(!s.equals(bpe.getModelInterface().getSimpleName())) {
377                                //i.e., type mismatch,
378                                //whereas (mergeDuplicates==true && bpe != null) is true already -
379                                throw new BioPaxIOException(String.format("processIndividual: " +
380                                                "despite mergeDuplicates is True, failed/skipped creating an instance " +
381                                                "of %s, URI:%s, because previously added object (same URI) was of different type: %s",
382                                                s, id, bpe.getModelInterface().getSimpleName()));
383                        }
384                } else {
385                        if (r.hasText())
386                        {
387                                log.warn("Unknown class :" + r.getText());
388                        } else
389                        {
390                                log.warn("Unknown class :" + r);
391                        }
392                        skip();
393                }
394                propertyContext = true;
395                r.next();
396                while (r.getEventType() != END_ELEMENT)
397                {
398                        if (r.getEventType() == START_ELEMENT)
399                        {
400                                processProperty(model, id);
401                                propertyContext = true;
402
403                        }
404                        r.next();
405                }
406                return id;
407        }
408
409        private void createBpe(String s, String id, Model model)
410        {
411                BioPAXElement bpe = factory.create(s, id);
412                model.add(bpe);
413        }
414
415        private void skip() throws XMLStreamException
416        {
417                int depth = 1;
418                while (!(r.getEventType() == END_ELEMENT && depth == 0))
419                {
420                        r.next();
421                        switch (r.getEventType())
422                        {
423                                case START_ELEMENT:
424                                        depth++;
425                                        break;
426                                case END_ELEMENT:
427                                        depth--;
428                                        break;
429                        }
430                }
431        }
432
433        public String getId()
434        {
435                String id = r.getAttributeValue(rdf, "ID");
436                if (id == null)
437                {
438                        id = r.getAttributeValue(rdf, "about");
439                        if (id.startsWith("#"))
440                        {
441                                id = base + id.substring(1, id.length());
442
443                        }
444                } else if (base != null)
445                {
446                        id = base + id;
447                }
448
449                return id;
450        }
451
452        private void processProperty(Model model, String ownerID) throws XMLStreamException
453        {
454                if (rdfs.equals(r.getNamespaceURI()) && "comment".equals(r.getLocalName()))
455                {
456                        BioPAXElement paxElement = model.getByID(ownerID);
457                        AbstractPropertyEditor commentor = getRDFCommentEditor(paxElement);
458                        r.next();
459                        assert r.getEventType() == CHARACTERS;
460                        String text = r.getText();
461                        commentor.setValueToBean(text, paxElement);
462                        log.warn("rdfs:comment is converted into the bp:comment; " +
463                                 "however, this can be overridden " +
464                                 "if there exists another bp:comment (element: " +
465                                 paxElement.getRDFId() + " text: " + text + ")");
466                        gotoEndElement();
467                } else if (bp != null && bp.equals(r.getNamespaceURI()))
468                {
469                        String property = r.getLocalName();
470                        String resource = r.getAttributeValue(rdf, "resource");
471                        if (resource != null)
472                        {
473                                if (resource.startsWith("#"))
474                                {
475                                        resource = (base == null ? "" : base) + resource.substring(1, resource.length());
476                                }
477                                gotoEndElement();
478                        } else
479                        {
480                                r.next();
481                                boolean found = false;
482                                while (r.getEventType() != END_ELEMENT)
483                                {
484                                        if (!found && r.getEventType() == CHARACTERS)
485                                        {
486                                                StringBuilder buff = new StringBuilder(r.getText());
487                                                r.next();
488                                                while (r.getEventType() == CHARACTERS)
489                                                {
490                                                        buff.append(r.getText());
491                                                        r.next();
492                                                }
493                                                resource = buff.toString();
494
495                                        } else if (r.getEventType() == START_ELEMENT)
496                                        {
497                                                propertyContext = false;
498                                                resource = processIndividual(model);
499                                                found = true;
500                                                r.next();
501                                        } else r.next();
502                                }
503                                resource = (!found && resource != null) ? resource.replaceAll("[\n\r\t ]+", " ") : resource;
504                        }
505
506                        log.trace("setting = " + resource);
507                        triples.add(new Triple(ownerID, resource, property));
508                        propertyContext = false;
509                } else
510                {
511                        log.trace("ignoring unknown element " +
512                                  r.getNamespaceURI() + r.getLocalName());
513                        gotoEndElement();
514                }
515
516        }
517
518        private void gotoEndElement() throws XMLStreamException
519        {
520                while (r.getEventType() != END_ELEMENT)
521                {
522                        r.next();
523                }
524        }
525
526
527        public class Triple
528        {
529                public String domain, range, property;
530
531                private Triple(String domain, String range, String property)
532                {
533                        this.domain = domain;
534                        this.range = range;
535                        this.property = property;
536
537                }
538
539                @Override
540                public String toString()
541                {
542                        return String.format("domain: %s, property: %s, range: %s", domain, property, range);
543                }
544
545        }
546
547
548        /**
549         * Converts a model into BioPAX (OWL) format, and writes it into
550         * the outputStream. Saved data can be then read via {@link BioPAXIOHandler}
551         * interface (e.g., {@link SimpleIOHandler}).
552         *
553         * Note: When the model is incomplete (i.e., contains elements that refer externals,
554         * dangling BioPAX elements) and is exported by this method, it works; however one
555         * will find corresponding object properties set to NULL later,
556         * after converting such data back to Model.
557         * 
558         * Note: if the model is very very large, and the output stream is a byte array stream,
559         * then you can eventually get OutOfMemoryError "Requested array size exceeds VM limit"
560         * (max. array size is 2Gb)
561         * 
562         * @param model model to be converted into OWL format
563         * @param outputStream output stream into which the output will be written
564         * @throws BioPaxIOException in case of I/O problems
565         */
566        public void convertToOWL(Model model, OutputStream outputStream)
567        {
568                initializeExporter(model);
569
570                try
571                {
572                        Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
573                        writeObjects(out, model);
574                        out.close();
575                }
576                catch (IOException e)
577                {
578                        throw new BioPaxIOException("Cannot convert to OWL!", e);
579                }
580        }
581
582
583        /**
584         * Writes the XML representation of individual BioPAX element that
585         * is BioPAX-like but only for display or debug purpose (incomplete).
586         *
587         * Note: use {@link BioPAXIOHandler#convertToOWL(org.biopax.paxtools.model.Model, java.io.OutputStream)}
588         * convertToOWL(org.biopax.paxtools.model.Model, Object)} instead
589         * if you have a model and want to save and later restore it.
590         * @param out output
591         * @param bean BioPAX object
592         * @throws IOException when the output writer throws
593         */
594        public void writeObject(Writer out, BioPAXElement bean) throws IOException
595        {
596                String name = "bp:" + bean.getModelInterface().getSimpleName();
597                writeIDLine(out, bean, name);
598
599                Set<PropertyEditor> editors = editorMap.getEditorsOf(bean);
600                if (editors == null || editors.isEmpty())
601                {
602                        log.info("no editors for " + bean.getRDFId() + " | " + bean.getModelInterface().getSimpleName());
603                        out.write(newline + "</" + name + ">");
604                        return;
605                }
606
607                for (PropertyEditor editor : editors)
608                {
609                        Set value = editor.getValueFromBean(bean); //is never null
610                        for (Object valueElement : value)
611                        {
612                                if (!editor.isUnknown(valueElement)) writeStatementFor(bean, editor, valueElement, out);
613                        }
614                }
615
616                out.write(newline + "</" + name + ">");
617        }
618
619
620        private void writeObjects(Writer out, Model model) throws IOException
621        {
622                writeHeader(out);
623
624                Set<BioPAXElement> bioPAXElements = model.getObjects();
625                for (BioPAXElement bean : bioPAXElements)
626                {
627                        writeObject(out, bean);
628                }
629
630                out.write(newline + "</rdf:RDF>");
631        }
632
633
634        private void writeStatementFor(BioPAXElement bean, PropertyEditor editor, Object value, Writer out)
635                        throws IOException
636        {
637                assert (bean != null && editor != null && value != null);
638
639                //fix (for L3 only): skip 'name' if it's present in the displayName, etc..
640                if (editor.getProperty().equalsIgnoreCase("name") && bean instanceof Named)
641                { // the latter maybe not necessary...
642                        Named named = (Named) bean;
643                        if(value.equals(named.getDisplayName()) || value.equals(named.getStandardName()))
644                        {
645                                return;
646                        }
647                }
648                
649                if (editor.getProperty().equalsIgnoreCase("stepProcess") && bean instanceof BiochemicalPathwayStep)
650                {
651                        BiochemicalPathwayStep bps = (BiochemicalPathwayStep) bean;
652                        if(value.equals(bps.getStepConversion()))
653                        {
654                                return;
655                        }
656                }
657
658                String prop = "bp:" + editor.getProperty();
659                out.write(newline + " <" + prop);
660
661                if (value instanceof BioPAXElement)
662                {
663                        String id = ((BioPAXElement) value).getRDFId();
664                        assert id != null;
665                        if (!absoluteUris && base != null && id.startsWith(base))
666                        {
667                                id = '#' + id.substring(base.length());
668                        }
669                        out.write(" rdf:resource=\"" + id + "\" />");
670                } else
671                {
672                        String type = findLiteralType(editor);
673                        String valString = StringEscapeUtils.escapeXml(value.toString());
674                        out.write(" rdf:datatype = \"" + xsd + type + "\">" + valString +
675                                  "</" + prop + ">");
676                }
677        }
678
679
680        private String findLiteralType(PropertyEditor editor)
681        {
682                Class range = editor.getRange();
683                String type = null;
684                if (range.isEnum() || range.equals(String.class))
685                {
686                        type = "string";
687                } else if (range.equals(double.class) || range.equals(Double.class))
688                {
689                        type = "double";
690                } else if (range.equals(int.class) || range.equals(Integer.class))
691                {
692                        type = "int";
693                } else if (range.equals(float.class) || range.equals(Float.class))
694                {
695                        type = "float";
696                } else if (range.equals(boolean.class) || range.equals(Boolean.class))
697                {
698                        type = "boolean";
699                } else if (range.equals(long.class) || range.equals(Long.class))
700                {
701                        type = "long";
702                }
703                return type;
704        }
705
706
707        private void writeIDLine(Writer out, BioPAXElement bpe, String name) throws IOException
708        {
709
710                out.write(newline + newline + "<" + name + " ");
711                String s = bpe.getRDFId();
712                if (!absoluteUris &&  base != null && s.startsWith(base))
713                {
714                        String id = s.substring(base.length());
715                        out.write(RDF_ID + id + close);
716                } else
717                {
718                        out.write(RDF_about + s + close);
719                }
720
721
722        }
723
724
725        private void initializeExporter(Model model)
726        {
727                base = model.getXmlBase();
728                namespaces = new HashMap<String, String>(model.getNameSpacePrefixMap());
729
730                normalizeNameSpaces(); // - for this reader/exporter tool
731                // also save the changes to the model?
732                if (normalizeNameSpaces)
733                {
734                        model.getNameSpacePrefixMap().clear();
735                        model.getNameSpacePrefixMap().putAll(namespaces);
736                }
737
738                BioPAXLevel lev = model.getLevel();
739                if (lev != this.level) resetLevel(lev, lev.getDefaultFactory());
740        }
741
742
743        private void normalizeNameSpaces()
744        {
745                String owlPre = null;
746                String rdfPre = null;
747                String xsdPre = null;
748
749                for (String pre : namespaces.keySet())
750                {
751                        String ns = namespaces.get(pre);
752                        if (rdfNS.equalsIgnoreCase(ns))
753                        {
754                                rdfPre = pre;
755                        } else if (owlNS.equalsIgnoreCase(ns))
756                        {
757                                owlPre = pre;
758                        } else if (xsdNS.equalsIgnoreCase(ns))
759                        {
760                                xsdPre = pre;
761                        }
762                }
763
764                if (owlPre != null)
765                {
766                        namespaces.remove(owlPre);
767                }
768
769                if (rdfPre != null)
770                {
771                        namespaces.remove(rdfPre);
772                }
773
774                if (xsdPre != null)
775                {
776                        namespaces.remove(xsdPre);
777                }
778
779                namespaces.put("rdf", rdfNS);
780                namespaces.put("owl", owlNS);
781                namespaces.put("xsd", xsdNS);
782        }
783
784
785        private void writeHeader(Writer out) throws IOException
786        {
787                //Header
788                out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
789                out.write(newline + "<rdf:RDF");
790                String bpns = this.editorMap.getLevel().getNameSpace();
791                for (String pre : namespaces.keySet())
792                {
793                        if (!pre.equals("bp"))
794                        {
795                                out.write(newline + " xmlns" +
796                                          (("".equals(pre)) ? "" : ":" + pre) + "=\"" + namespaces.get(pre) + "\"");
797                        }
798                }
799                // write 'xmlns:bp'
800                out.write(newline + " xmlns:bp" + "=\"" + bpns + "\"");
801
802                if (base != null)
803                {
804                        out.write(newline + " xml:base=\"" + base + "\"");
805                }
806
807                out.write(">");
808                out.write(newline + "<owl:Ontology rdf:about=\"\">");
809                out.write(newline + " <owl:imports rdf:resource=\"" + bpns + "\" />");
810                out.write(newline + "</owl:Ontology>");
811        }
812
813        
814        /**
815         * Sets the flag used when exporting a BioPAX model to RDF/XML:
816         * true - to always write full URI in rdf:resource and use 
817         * rdf:about instead rdf:ID (does not matter xml:base is set or not).
818         * This is good for the data loading a RDF/SPARQL servers, such as Virtuoso,
819         * so they generate correct and resolvable links from BioPAX URIs
820         * (use of rdf:ID="localId" and rdf:resource="#localID" is known to make
821         * them insert extra '#' between xml:base and localId).
822         * 
823         * @param absoluteUris true/false - whether to force writing absolute URIs (thus, never use rdf:ID)
824         */
825        public void absoluteUris(boolean absoluteUris) {
826                this.absoluteUris = absoluteUris;
827        }
828                
829        /**
830         * @see #absoluteUris(boolean)
831         * @return true/false
832         */
833        public boolean isAbsoluteUris()
834        {
835                return this.absoluteUris;
836        }
837        
838
839        /**
840         * Sets the flag used when exporting a BioPAX model to RDF/XML:
841         * true - to clean up current namespaces (e.g., those read from a file)
842         * and use defaults instead (prefixes: 'rdf', 'rdfs', 'owl', 'xsd')
843         * @param normalizeNameSpaces true/false
844         */
845        public void normalizeNameSpaces(boolean normalizeNameSpaces)
846        {
847                this.normalizeNameSpaces = normalizeNameSpaces;
848        }
849
850        /**
851         * @see #normalizeNameSpaces()
852         * @return true/false
853         */
854        public boolean isNormalizeNameSpaces()
855        {
856                return this.normalizeNameSpaces;
857        }
858
859        /**
860         * @see #mergeDuplicates(boolean)
861         * @return true/false
862         */
863        public boolean isMergeDuplicates()
864        {
865                return this.mergeDuplicates;
866        }
867
868
869        /**
870         * Serializes a (not too large) BioPAX model to the RDF/XML (OWL) formatted string.
871         *
872         * @param model a BioPAX object model to convert to the RDF/XML format
873         * @return the BioPAX data in the RDF/XML format
874         * @throws IllegalArgumentException if model is null
875         * @throws OutOfMemoryError when it cannot be stored in a byte array (max 2Gb).
876         */
877        public static String convertToOwl(Model model)
878        {
879                if (model == null) throw new IllegalArgumentException();
880
881                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
882
883                (new SimpleIOHandler(model.getLevel())).convertToOWL(model, outputStream);
884
885                try
886                {
887                        return outputStream.toString("UTF-8");
888                }
889                catch (UnsupportedEncodingException e)
890                {
891                        log.error(e);
892                        return outputStream.toString();
893                }
894        }
895
896}
897