001package org.biopax.paxtools.io.sif;
002
003import org.apache.commons.lang.StringUtils;
004import org.apache.commons.logging.Log;
005import org.apache.commons.logging.LogFactory;
006import org.biopax.paxtools.controller.PathAccessor;
007import org.biopax.paxtools.io.sif.level3.Group;
008import org.biopax.paxtools.io.sif.level3.InteractionSetL3;
009import org.biopax.paxtools.model.BioPAXElement;
010import org.biopax.paxtools.model.BioPAXLevel;
011import org.biopax.paxtools.model.Model;
012import org.biopax.paxtools.model.level2.complex;
013import org.biopax.paxtools.model.level2.physicalEntity;
014import org.biopax.paxtools.model.level2.physicalEntityParticipant;
015import org.biopax.paxtools.model.level3.EntityReference;
016import org.biopax.paxtools.model.level3.PhysicalEntity;
017import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.io.OutputStreamWriter;
022import java.io.Writer;
023import java.util.*;
024
025/**
026 * Converts a BioPAX model to SIF (simple interaction format), by inferring the interactions in the
027 * model, and describing them in terms of simple interactions.
028 *
029 * @deprecated use the pattern (new Paxtools' module) api instead
030 */
031public class SimpleInteractionConverter
032{
033        /**
034         * Rules used during generation of interactions.
035         */
036        private final InteractionRule[] rules;
037
038        /**
039         * Log for logging.
040         */
041        private final Log log = LogFactory.getLog(SimpleInteractionConverter.class);
042
043        /**
044         * Options for interaction generation.
045         */
046        private final Map options;
047
048        /**
049         * Option to reduce complexes or use them as they are in interactions.
050         */
051        public static final String REDUCE_COMPLEXES = "REDUCE_COMPLEXES";
052
053        /**
054         * Option to reduce complexes or use them as they are in interactions.
055         */
056        public static final String REDUCE_GENERICS = "REDUCE_GENERICS";
057
058        /**
059         * IDs of unwanted elements in SIF graph.
060         */
061        private Set<String> blackList;
062
063        /**
064         * Constructor with the mining rules to use.
065         * @param rules interaction rule set to be used in the conversion
066         */
067        public SimpleInteractionConverter(InteractionRule... rules)
068        {
069                this(new HashMap(), rules);
070        }
071
072        /**
073         * Constructor with mining rules and options.
074         * @param options options to be used during the conversion process
075         * @param rules interaction rule set to be used in the conversion
076         */
077        public SimpleInteractionConverter(Map options, InteractionRule... rules)
078        {
079                this(options, null, rules);
080        }
081
082        /**
083         * Constructor with mining rules, options, and black list.
084         * @param options options to be used during the conversion process
085         * @param blackList ids of molecules that we do not want them in SIF
086         * @param rules interaction rule set to be used in the conversion
087         */
088        public SimpleInteractionConverter(Map options, Set<String> blackList, InteractionRule... rules)
089        {
090
091                this.blackList = blackList;
092                this.rules = rules;
093                this.options = options;
094                for (InteractionRule rule : rules)
095                {
096                        rule.initOptions(options);
097                }
098        }
099
100        /**
101         * Infers simple interactions from the interactions found in the <em>model</em> for every
102         * interaction rule given; and returns this inferred simple interactions.
103         * @param model model from which simple interactions are going to be inferred
104         * @return a set of inferred simple interactions
105         */
106        public Set<SimpleInteraction> inferInteractions(Model model)
107        {
108
109                switch (model.getLevel())
110                {
111                        case L1:
112                        case L2:
113                                return inferL2(model);
114                        case L3:
115                                return inferL3(model);
116                        default:
117                                throw new IllegalBioPAXArgumentException("Unknown BioPAX Level");
118                }
119        }
120
121        /**
122         * Infers interactions for L3 models.
123         * @param model L3 model
124         * @return inferred interactions
125         */
126        private Set<SimpleInteraction> inferL3(Model model)
127        {
128                InteractionSetL3 interactions = new InteractionSetL3(model);
129
130                Set<PhysicalEntity> bioPAXElements = model.getObjects(PhysicalEntity.class);
131                for (PhysicalEntity er : bioPAXElements)
132                {
133                        for (InteractionRule rule : rules)
134                        {
135                                tryInferringRule(model, interactions, er, rule);
136                        }
137                }
138        interactions.convertGroupsToInteractions();
139                if(options.containsKey(REDUCE_COMPLEXES))
140                {
141                        System.out.println("reducing groups");
142                        InteractionSetL3 reduced = new InteractionSetL3(model);
143                        for (SimpleInteraction interaction : interactions)
144                        {
145                                reduceL3Groups(interaction, reduced);
146                        }
147                        interactions = reduced;
148                }
149                if (blackList != null)
150                {
151                        removeInteractionsWithBlackListMolecules(interactions, blackList);
152                }
153                return interactions;
154        }
155
156        /**
157         * Tries to use a rule to infer interactions. If an exception occurs other than
158         * MaximumInteractionThresholdExceedException, logs the error and continues.
159         * @param model model to use
160         * @param interactions inferred interactions
161         * @param bpe base element for inference
162         * @param rule current interaction rule
163         * @exception MaximumInteractionThresholdExceedException if generated rules are over a certain
164         * number
165         */
166        private void tryInferringRule(Model model, InteractionSet interactions, BioPAXElement bpe,
167                        InteractionRule rule)
168        {
169                try
170                {
171                        rule.inferInteractions(interactions, bpe, model);
172                }
173                catch (MaximumInteractionThresholdExceedException e)
174                {
175                                throw e;
176                }
177                catch (Exception e)
178                {
179                        log.error("Exception while applying rule :" + this.getClass().getSimpleName() + "to the element: " +
180                                  bpe.getRDFId(), e);
181                }
182        }
183
184        /**
185         */
186        private void reduceL3Groups(SimpleInteraction int2reduce, Set<SimpleInteraction> reducedInts)
187        {
188                if (!(int2reduce.getType() == BinaryInteractionType.COMPONENT_OF))
189                {
190                        HashSet<EntityReference> sourceSet = new HashSet<EntityReference>();
191                        HashSet<EntityReference> targetSet = new HashSet<EntityReference>();
192
193                        reduceL3Groups(int2reduce.getSource(), sourceSet);
194                        reduceL3Groups(int2reduce.getTarget(), targetSet);
195                        for (EntityReference source : sourceSet)
196                        {
197                                for (EntityReference target : targetSet)
198                                {
199                                        SimpleInteraction interaction = new SimpleInteraction(source, target, int2reduce.getType());
200                                        interaction.getMediators().addAll(int2reduce.getMediators());
201                                        reducedInts.add(interaction);
202                                }
203                        }
204                }
205        }
206
207        /**
208         */
209        private static void reduceL3Groups(BioPAXElement bpe, Set<EntityReference> reduced)
210        {
211                if (bpe instanceof Group)
212                {
213                        reduced.addAll(((Group) bpe).getAllSimpleMembers());
214                }
215                else
216                {
217                        reduced.add((EntityReference) bpe);
218                }
219        }
220
221
222
223        /**
224         * Infers interactions on L2 models.
225         * @param model L2 model
226         * @return inferred interactions
227         */
228        private Set<SimpleInteraction> inferL2(Model model)
229        {
230                InteractionSet interactions = new InteractionSet();
231                Set<physicalEntity> bioPAXElements = model.getObjects(physicalEntity.class);
232
233                for (physicalEntity pe : bioPAXElements)
234                {
235                        for (InteractionRule rule : rules)
236                        {
237                                tryInferringRule(model, interactions, pe, rule);
238                        }
239                }
240                if (this.options.containsKey(REDUCE_COMPLEXES))
241                {
242                        InteractionSet reduced = new InteractionSet();
243                        for (SimpleInteraction si : interactions)
244                        {
245                                reduceL2Complexes(si, reduced);
246                        }
247                        interactions = reduced;
248                }
249
250                log.info(interactions.size() + " interactions inferred");
251                return interactions;
252        }
253
254
255        /**
256         * If an interaction contains a complex as source or target, then this method creates new
257         * interactions using the members of the complex. If both ends are complex with members of size
258         * n and m, there will be n x m reduced interactions.
259         * @param reducedInts new interactions generated with complex members
260         */
261        private void reduceL2Complexes(SimpleInteraction int2reduce, Set<SimpleInteraction> reducedInts)
262        {
263                if (!(int2reduce.getType() == BinaryInteractionType.COMPONENT_OF))
264                {
265                        Set<physicalEntity> sourceSet = new HashSet<physicalEntity>();
266                        Set<physicalEntity> targetSet = new HashSet<physicalEntity>();
267
268                        recursivelyReduceL2Complexes(int2reduce.getSource(), sourceSet);
269                        recursivelyReduceL2Complexes(int2reduce.getTarget(), targetSet);
270                        for (physicalEntity source : sourceSet)
271                        {
272                                for (physicalEntity target : targetSet)
273                                {
274                                        SimpleInteraction interaction = new SimpleInteraction(source, target, int2reduce.getType());
275                                        interaction.getMediators().addAll(int2reduce.getMediators());
276                                        reducedInts.add(interaction);
277                                }
278                        }
279                }
280        }
281
282        /**
283         * Gets member physicalEntity of the complex recursively.
284         * @param bpe element to get the related physicalEntity
285         * @param reduced related physicalEntity set
286         */
287        private static void recursivelyReduceL2Complexes(BioPAXElement bpe, Set<physicalEntity> reduced)
288        {
289                if (bpe instanceof physicalEntityParticipant)
290                {
291                        recursivelyReduceL2Complexes(((physicalEntityParticipant) bpe).getPHYSICAL_ENTITY(), reduced);
292                } else
293                {
294                        if (bpe instanceof complex)
295                        {
296                                for (physicalEntityParticipant pep : ((complex) bpe).getCOMPONENTS())
297                                {
298                                        physicalEntity pe = pep.getPHYSICAL_ENTITY();
299                                        recursivelyReduceL2Complexes(pe, reduced);
300
301                                }
302
303                        } else if (bpe instanceof physicalEntity)
304                        {
305                                reduced.add((physicalEntity) bpe);
306                        }
307                }
308        }
309
310
311        /**
312         * Filters out interactions whose source or target are in black list.
313         * @param interactions interactions to filter
314         * @param blackList IDs of unwanted elements
315         */
316        protected void removeInteractionsWithBlackListMolecules(Set<SimpleInteraction> interactions,
317                        Set<String> blackList)
318        {
319                Iterator<SimpleInteraction> iter = interactions.iterator();
320                while (iter.hasNext())
321                {
322                        SimpleInteraction inter = iter.next();
323                        if (blackList.contains(inter.getSource().getRDFId()) ||
324                                blackList.contains(inter.getTarget().getRDFId()) ||
325                                intersects(blackList, inter.getMediators()))
326                        {
327                                iter.remove();
328                        }
329                }
330        }
331
332        /**
333         * Checks if the blacklist contains id of any mediator
334         * @param ids blacklist
335         * @param mediators mediators of inferred interactions
336         * @return true if any mediator is blacklisted
337         */
338        private boolean intersects(Set<String> ids, Set<BioPAXElement> mediators)
339        {
340                for (BioPAXElement mediator : mediators)
341                {
342                        if (ids.contains(mediator.getRDFId())) return true;
343                }
344                return false;
345        }
346
347        /**
348         * Infers simple interactions from the <em>model</em> using {@link
349         * #inferInteractions(org.biopax.paxtools.model.Model)} and wrties them to an output stream.
350         * @param model model from which simple interactions are going to be inferred
351         * @param out output stream to which simple interactions will be written
352         * @exception IOException in case of problems with output.
353         */
354        public void writeInteractionsInSIF(Model model, OutputStream out) throws IOException
355        {
356                Set<SimpleInteraction> interactions = inferInteractions(model);
357                Writer writer = new OutputStreamWriter(out);
358                for (SimpleInteraction simpleInteraction : interactions)
359                {
360                        writer.write(simpleInteraction.toString() + "\n");
361                }
362                writer.close();
363        }
364
365        /**
366         * This method outputs inferred interactions in SIF annotation extended format (Sifnx).
367         *
368         * It's a tab-delimited text data format. The result is written to two separate files or streams -
369         * for edges and nodes, respectively (also, files can later be merged into one having there
370         * two sections separated by single blank line; we still call it Sifnx format).
371         *
372         * The first section/file (that describes interactions) is similar to SIF, however,
373         * there might be publication references (optional) next to each interaction line, etc.
374         *
375         * The second one (about interacting entities, i.e., nodes), lists the IDs and corresponding
376         * BioPAX property values users wanted from a BioPAX model, for each participant.
377         * It is in the form: "ID\tproperty_value\tproperty_value\tproperty_values...".
378         * If the cardinality of property is multiple, the values are separated by a semi column;
379         * e.g.:
380         *
381         * id    aName   uniprot:P12345;entrez-gene:1234
382         *
383         * @param model model to convert
384         * @param edgeStream output stream for interactions (edges)
385         * @param nodeStream output stream for nodes (second section)
386         * @param interactorPropertyPaths interactor property paths
387         * @param mediatorPropertyPaths mediator property paths
388         * @param writeEntityTypes whether to output participants' BioPAX types
389         * @exception IOException when there is an output stream error
390         */
391        public void writeInteractionsInSIFNX(Model model, OutputStream edgeStream, OutputStream nodeStream,
392                        List<String> interactorPropertyPaths, List<String> mediatorPropertyPaths, boolean writeEntityTypes)
393                        throws IOException
394        {
395                Set<SimpleInteraction> interactions = inferInteractions(model);
396                Set<BioPAXElement> entities = new HashSet<BioPAXElement>();
397                List<PathAccessor> interactorAccessors = null;
398                List<PathAccessor> mediatorAccessors = null;
399
400                if (interactorPropertyPaths != null)
401                {
402                        interactorAccessors = new ArrayList<PathAccessor>(interactorPropertyPaths.size());
403                        for (String s : interactorPropertyPaths)
404                        {
405                                interactorAccessors.add(new PathAccessor(s, model.getLevel()));
406                        }
407                }
408                if (mediatorPropertyPaths != null)
409                {
410                        mediatorAccessors = new ArrayList<PathAccessor>(mediatorPropertyPaths.size());
411                        for (String s : mediatorPropertyPaths)
412                        {
413                                mediatorAccessors.add(new PathAccessor(s, model.getLevel()));
414                        }
415                }
416
417                Set<String> lines = new TreeSet<String>(); //this is also to avoid duplicate records
418                for (SimpleInteraction si : interactions)
419                {
420                        StringBuilder sb = new StringBuilder(si.toString());
421                        entities.add(si.getSource());
422                        entities.add(si.getTarget());
423                        if (mediatorAccessors != null)
424                        {
425                                for (PathAccessor mediatorAccessor : mediatorAccessors)
426                                {
427
428                                        HashSet values = new HashSet();
429
430                                        for (BioPAXElement mediator : si.getMediators())
431                                        {
432                                                if (mediatorAccessor.applies(mediator))
433                                                {
434                                                        values.add(valuesToString(mediatorAccessor.getValueFromBean(mediator)));
435                                                }
436                                        }
437                                        sb.append("\t").append(values.isEmpty() ? "not applicable" : valuesToString(values));
438                                }
439                        }
440                        lines.add(sb.toString());
441                }
442
443                Writer writer = new OutputStreamWriter(edgeStream);
444                writer.write(StringUtils.join(lines, "\n"));
445                writer.flush();
446                
447                // now write nodes info
448                writer = new OutputStreamWriter(nodeStream);
449                for (BioPAXElement entity : entities)
450                {
451                        if (entity != null)
452                        {
453
454                                writer.write(entity.getRDFId());
455                                if (writeEntityTypes) writer.write("\t" + getEntityTypeString(entity));
456                                if (interactorAccessors != null)
457                                {
458                                        for (PathAccessor accessor : interactorAccessors)
459                                        {
460                                                writer.write("\t");
461                                                if (accessor == null ||!accessor.applies(entity)) writer.write("(not applicable)");
462                                                else if (accessor.isUnknown(entity)) writer.write("(not specified)");
463                                                else
464                                                {
465                                                        Set values = accessor.getValueFromBean(entity);
466                                                        writer.write(valuesToString(values));
467                                                }
468                                        }
469                                }
470
471                                writer.write("\n");
472                        }
473                }
474
475                writer.close();
476        }
477
478        /**
479         * Gets the type of the element.
480         * @param entity entity to ask its type
481         * @return type in string
482         */
483        private String getEntityTypeString(BioPAXElement entity)
484        {
485                if(entity instanceof Group)
486                {
487                        return ((Group) entity).groupTypeToString();
488                }
489                else
490                        return entity.getModelInterface().getSimpleName();
491        }
492
493        /**
494         * Prepared a semicolon separated string of values.
495         * @param values values to list
496         * @return string representing values
497         */
498        private String valuesToString(Set values)
499        {
500                StringBuilder bldr = new StringBuilder();
501                for (Object value : values)
502                {
503                        bldr.append(value).append(";");
504                }
505                if (bldr.length() > 0) bldr.deleteCharAt(bldr.length() - 1);
506                return bldr.toString();
507        }
508
509        /**
510         * Gets all available interaction rules for the given level.
511         * @param level BioPAX level
512         * @return available rules
513         */
514        public static List<InteractionRule> getRules(BioPAXLevel level)
515        {
516                List<InteractionRule> list = new ArrayList<InteractionRule>(5);
517                if (level == BioPAXLevel.L2)
518                {
519                        list.add(new org.biopax.paxtools.io.sif.level2.ComponentRule());
520                        list.add(new org.biopax.paxtools.io.sif.level2.ConsecutiveCatalysisRule());
521                        list.add(new org.biopax.paxtools.io.sif.level2.ControlRule());
522                        list.add(new org.biopax.paxtools.io.sif.level2.ControlsTogetherRule());
523                        list.add(new org.biopax.paxtools.io.sif.level2.ParticipatesRule());
524                        list.add(new org.biopax.paxtools.io.sif.level2.AffectsRule());
525                } else if (level == BioPAXLevel.L3)
526                {
527                        list.add(new org.biopax.paxtools.io.sif.level3.ComponentRule());
528                        list.add(new org.biopax.paxtools.io.sif.level3.ConsecutiveCatalysisRule());
529                        list.add(new org.biopax.paxtools.io.sif.level3.ControlRule());
530                        list.add(new org.biopax.paxtools.io.sif.level3.ControlsTogetherRule());
531                        list.add(new org.biopax.paxtools.io.sif.level3.ParticipatesRule());
532                        list.add(new org.biopax.paxtools.io.sif.level3.ExpressionRule());
533                }
534                return list;
535        }
536}