001package org.biopax.paxtools.query;
002
003import org.biopax.paxtools.model.BioPAXElement;
004import org.biopax.paxtools.model.BioPAXLevel;
005import org.biopax.paxtools.model.Model;
006import org.biopax.paxtools.model.level3.*;
007import org.biopax.paxtools.query.algorithm.*;
008import org.biopax.paxtools.query.model.Graph;
009import org.biopax.paxtools.query.model.GraphObject;
010import org.biopax.paxtools.query.model.Node;
011import org.biopax.paxtools.query.wrapperL3.Filter;
012import org.biopax.paxtools.query.wrapperL3.GraphL3;
013import org.biopax.paxtools.query.wrapperL3undirected.GraphL3Undirected;
014
015import java.util.*;
016
017/**
018 * This class provides static methods to execute graph queries. These cover only the most frequent
019 * use cases. Users can use these methods as example for executing the query they need.
020 *
021 * @author Ozgun Babur
022 */
023public class QueryExecuter
024{
025        /**
026         * Gets neighborhood of the source set.
027         *
028         * @param sourceSet seed to the query
029         * @param model BioPAX model
030         * @param limit neigborhood distance to get
031         * @param direction UPSTREAM, DOWNSTREAM or BOTHSTREAM
032         * @param filters for filtering graph elements
033         * @return BioPAX elements in the result set
034         */
035        public static Set<BioPAXElement> runNeighborhood(
036                Set<BioPAXElement> sourceSet,
037                Model model,
038                int limit,
039                Direction direction,
040                Filter... filters)
041        {
042                Graph graph;
043
044                if (model.getLevel() == BioPAXLevel.L3)
045                {
046                        if (direction == Direction.UNDIRECTED)
047                        {
048                                graph = new GraphL3Undirected(model, filters);
049                                direction = Direction.BOTHSTREAM;
050                        }
051                        else
052                        {
053                                graph = new GraphL3(model, filters);
054                        }
055                }
056                else return Collections.emptySet();
057
058                Set<Node> source = prepareSingleNodeSet(sourceSet, graph);
059
060                if (sourceSet.isEmpty()) return Collections.emptySet();
061
062                NeighborhoodQuery query = new NeighborhoodQuery(source, direction, limit);
063                Set<GraphObject> resultWrappers = query.run();
064                return convertQueryResult(resultWrappers, graph, true);
065        }
066
067        /**
068         * Gets the graph constructed by the paths between the given seed nodes. Does not get paths
069         * between physical entities that belong the same entity reference.
070         * @param sourceSet Seed to the query
071         * @param model BioPAX model
072         * @param limit Length limit for the paths to be found
073         * @param filters optional filters - for filtering graph elements
074         * @return BioPAX elements in the result
075         */
076        public static Set<BioPAXElement> runPathsBetween(Set<BioPAXElement> sourceSet, Model model,
077                int limit, Filter... filters)
078        {
079                Graph graph;
080
081                if (model.getLevel() == BioPAXLevel.L3)
082                {
083                        graph = new GraphL3(model, filters);
084                }
085                else return Collections.emptySet();
086
087                Collection<Set<Node>> sourceWrappers = prepareNodeSets(sourceSet, graph);
088
089                if (sourceSet.size() < 2) return Collections.emptySet();
090
091                PathsBetweenQuery query = new PathsBetweenQuery(sourceWrappers, limit);
092                Set<GraphObject> resultWrappers = query.run();
093                return convertQueryResult(resultWrappers, graph, true);
094        }
095
096        /**
097         * Gets paths between the seed nodes.
098         * @param sourceSet Seed to the query
099         * @param model BioPAX model
100         * @param limit Length limit for the paths to be found
101         * @param filters for filtering graph elements
102         * @return BioPAX elements in the result
103         * @deprecated Use runPathsBetween instead
104         */
105        public static Set<BioPAXElement> runGOI(
106                Set<BioPAXElement> sourceSet,
107                Model model,
108                int limit,
109                Filter... filters)
110        {
111                return runPathsFromTo(sourceSet, sourceSet, model, LimitType.NORMAL, limit, filters);
112        }
113
114        /**
115         * Gets paths the graph composed of the paths from a source node, and ends at a target node.
116         * @param sourceSet Seeds for start points of paths
117         * @param targetSet Seeds for end points of paths
118         * @param model BioPAX model
119         * @param limitType either NORMAL or SHORTEST_PLUS_K
120         * @param limit Length limit fothe paths to be found
121         * @param filters for filtering graph elements
122         * @return BioPAX elements in the result
123         */
124        public static Set<BioPAXElement> runPathsFromTo(
125                Set<BioPAXElement> sourceSet,
126                Set<BioPAXElement> targetSet,
127                Model model,
128                LimitType limitType,
129                int limit,
130                Filter... filters)
131        {
132                Graph graph;
133
134                if (model.getLevel() == BioPAXLevel.L3)
135                {
136                        graph = new GraphL3(model, filters);
137                }
138                else return Collections.emptySet();
139
140                Set<Node> source = prepareSingleNodeSet(sourceSet, graph);
141                Set<Node> target = prepareSingleNodeSet(targetSet, graph);
142
143                PathsFromToQuery query = new PathsFromToQuery(source, target, limitType, limit, true);
144                Set<GraphObject> resultWrappers = query.run();
145                return convertQueryResult(resultWrappers, graph, true);
146        }
147
148        /**
149         * Gets the elements in the common upstream or downstream of the seed
150         * @param sourceSet Seed to the query
151         * @param model BioPAX model
152         * @param direction UPSTREAM or DOWNSTREAM
153         * @param limit Length limit for the search
154         * @param filters for filtering graph elements
155         * @return BioPAX elements in the result
156         */
157        public static Set<BioPAXElement> runCommonStream(
158                Set<BioPAXElement> sourceSet,
159                Model model,
160                Direction direction,
161                int limit,
162                Filter... filters)
163        {
164                Graph graph;
165
166                if (model.getLevel() == BioPAXLevel.L3)
167                {
168                        graph = new GraphL3(model, filters);
169                }
170                else return Collections.emptySet();
171
172                Collection<Set<Node>> source = prepareNodeSets(sourceSet, graph);
173
174                if (sourceSet.size() < 2) return Collections.emptySet();
175
176                CommonStreamQuery query = new CommonStreamQuery(source, direction, limit);
177
178                Set<GraphObject> resultWrappers = query.run();
179                return convertQueryResult(resultWrappers, graph, false);
180        }
181
182        /**
183         * First finds the common stream, then completes it with the paths between seed and common
184         * stream.
185         * @param sourceSet Seed to the query
186         * @param model BioPAX model
187         * @param direction UPSTREAM or DOWNSTREAM
188         * @param limit Length limit for the search
189         * @param filters for filtering graph elements
190         * @return BioPAX elements in the result
191         */
192        public static Set<BioPAXElement> runCommonStreamWithPOI(
193                Set<BioPAXElement> sourceSet,
194                Model model,
195                Direction direction,
196                int limit,
197                Filter... filters)
198        {
199                Graph graph;
200
201                if (model.getLevel() == BioPAXLevel.L3)
202                {
203                        graph = new GraphL3(model, filters);
204                }
205                else return Collections.emptySet();
206
207                Collection<Set<Node>> sourceSets = prepareNodeSets(sourceSet, graph);
208
209                if (sourceSet.size() < 2) return Collections.emptySet();
210
211                // Run a common stream query
212
213                CommonStreamQuery commStream = new CommonStreamQuery(sourceSets, direction, limit);
214
215                Set<GraphObject> resultWrappers = commStream.run();
216
217                // Stop if they have no common stream.
218                if (resultWrappers.isEmpty()) return Collections.emptySet();
219
220                // Extract nodes from the result
221
222                Set<Node> target = new HashSet<Node>();
223
224                for (GraphObject go : resultWrappers)
225                {
226                        if (go instanceof Node) target.add((Node) go);
227                }
228
229                // Take union of the sources
230
231                Set<Node> source = new HashSet<Node>();
232                for (Set<Node> set : sourceSets)
233                {
234                        source.addAll(set);
235                }
236
237                // Run a paths-of-interest query between source set and result set
238
239                PathsFromToQuery poi;
240
241                if (direction == Direction.DOWNSTREAM)
242                {
243                        poi = new PathsFromToQuery(source, target, LimitType.NORMAL, limit, true);
244                }
245                else
246                {
247                        poi = new PathsFromToQuery(target, source, LimitType.NORMAL, limit, true);
248                }
249
250                resultWrappers = poi.run();
251                return convertQueryResult(resultWrappers, graph, true);
252        }
253
254        /**
255         * Converts the query result from wrappers to wrapped BioPAX elements.
256         * @param resultWrappers Wrappers of the result set
257         * @param graph Queried graph
258         * @param removeDisconnected whether to remove disconnected non-complex type physical entities
259         * @return Set of elements in the result
260         */
261        private static Set<BioPAXElement> convertQueryResult(
262                Set<GraphObject> resultWrappers, Graph graph, boolean removeDisconnected)
263        {
264                Set<Object> result = graph.getWrappedSet(resultWrappers);
265
266                Set<BioPAXElement> set = new HashSet<BioPAXElement>();
267                for (Object o : result)
268                {
269                        set.add((BioPAXElement) o);
270                }
271
272                // remove disconnected simple physical entities
273                if (removeDisconnected)
274                {
275                        Set<BioPAXElement> remove = new HashSet<BioPAXElement>();
276
277                        for (BioPAXElement ele : set)
278                        {
279                                if (ele instanceof SimplePhysicalEntity &&
280                                        isDisconnected((SimplePhysicalEntity) ele, set))
281                                {
282                                        remove.add(ele);
283                                }
284                        }
285                        set.removeAll(remove);
286                }
287                
288                return set;
289        }
290
291        private static boolean isDisconnected(SimplePhysicalEntity spe, Set<BioPAXElement> resultSet)
292        {
293                for (Interaction inter : spe.getParticipantOf())
294                {
295                        if (resultSet.contains(inter)) return false;
296                }
297
298                for (Complex complex : spe.getComponentOf())
299                {
300                        if (resultSet.contains(complex)) return false;
301                }
302
303                for (PhysicalEntity pe : spe.getMemberPhysicalEntityOf())
304                {
305                        if (resultSet.contains(pe)) return false;
306                }
307
308                for (PhysicalEntity pe : spe.getMemberPhysicalEntity())
309                {
310                        if (resultSet.contains(pe)) return false;
311                }
312
313                return true;
314        }
315
316        /**
317         * Gets the related wrappers of the given elements in a set.
318         * @param elements Elements to get the related wrappers
319         * @param graph Owner graph
320         * @return Related wrappers in a set
321         */
322        public static Set<Node> prepareSingleNodeSet(Set<BioPAXElement> elements, Graph graph)
323        {
324                Map<BioPAXElement, Set<PhysicalEntity>> map = getRelatedPhysicalEntityMap(elements);
325
326                Set<PhysicalEntity> pes = new HashSet<PhysicalEntity>();
327                for (Set<PhysicalEntity> valueSet : map.values())
328                {
329                        pes.addAll(valueSet);
330                }
331
332                Set<Node> nodes = graph.getWrapperSet(pes);
333
334                // If there are interactions in the seed add them too
335
336                Set<Node> inters = getSeedInteractions(elements, graph);
337                nodes.addAll(inters);
338
339                return nodes;
340        }
341
342        /**
343         * Gets the related wrappers of the given elements in individual sets. An object can be related
344         * to more than one wrapper and they will appear in the same set. This method created a set for
345         * each parameter element that has a related wrapper.
346         * @param elements Elements to get the related wrappers
347         * @param graph Owner graph
348         * @return Related wrappers in individual sets
349         */
350        private static Collection<Set<Node>> prepareNodeSets(Set<BioPAXElement> elements, Graph graph)
351        {
352                Collection<Set<Node>> sets = new HashSet<Set<Node>>();
353
354                Map<BioPAXElement, Set<PhysicalEntity>> map = getRelatedPhysicalEntityMap(elements);
355
356                for (Set<PhysicalEntity> pes : map.values())
357                {
358                        Set<Node> set = graph.getWrapperSet(pes);
359
360                        if (!set.isEmpty()) sets.add(set);
361                }
362                
363                // Add interactions in the seed as single node set
364
365                Set<Node> inters = getSeedInteractions(elements, graph);
366                for (Node node : inters)
367                {
368                        sets.add(Collections.singleton(node));
369                }
370
371                return sets;
372        }
373
374        /**
375         * Maps each BioPAXElement to its related PhysicalEntity objects.
376         *
377         * @param elements Elements to map
378         * @return The mapping
379         */
380        public static Map<BioPAXElement, Set<PhysicalEntity>> getRelatedPhysicalEntityMap(
381                Collection<BioPAXElement> elements)
382        {
383                replaceXrefsWithRelatedER(elements);
384                Map<BioPAXElement, Set<PhysicalEntity>> map = new HashMap<BioPAXElement, Set<PhysicalEntity>>();
385
386                for (BioPAXElement ele : elements)
387                {
388                        Set<PhysicalEntity> ents = getRelatedPhysicalEntities(ele, null);
389
390                        if (!ents.isEmpty())
391                        {
392                                map.put(ele, ents);
393                        }
394                }
395                return map;
396        }
397
398        /**
399         * Replaces Xref objects with the related EntityReference objects. This is required for the use
400         * case when user provides multiple xrefs that point to the same ER.
401         * @param elements elements to send to a query as source or target
402         */
403        protected static void replaceXrefsWithRelatedER(
404                Collection<BioPAXElement> elements)
405        {
406                Set<EntityReference> ers = new HashSet<EntityReference>();
407                Set<Xref> xrefs = new HashSet<Xref>();
408                for (BioPAXElement element : elements)
409                {
410                        if (element instanceof Xref)
411                        {
412                                xrefs.add((Xref) element);
413                                for (XReferrable able : ((Xref) element).getXrefOf())
414                                {
415                                        if (able instanceof EntityReference)
416                                        {
417                                                ers.add((EntityReference) able);
418                                        }
419                                }
420                        }
421                }
422
423                elements.removeAll(xrefs);
424                for (EntityReference er : ers)
425                {
426                        if (!elements.contains(er)) elements.add(er);
427                }
428        }
429
430        /**
431         * Gets the related PhysicalEntity objects of the given BioPAXElement, in level 3 models.
432         *
433         * @param element Element to get related PhysicalEntity objects
434         * @param pes Result set. If not supplied, a new set will be initialized.
435         * @return Related PhysicalEntity objects
436         */
437        public static Set<PhysicalEntity> getRelatedPhysicalEntities(BioPAXElement element,
438                Set<PhysicalEntity> pes)
439        {
440                if (pes == null) pes = new HashSet<PhysicalEntity>();
441
442                if (element instanceof Complex)
443                {
444                        Complex cpx = (Complex) element;
445
446                        if (!pes.contains(cpx))
447                        {
448                                pes.add(cpx);
449                                for (Complex parent : cpx.getComponentOf())
450                                {
451                                        getRelatedPhysicalEntities(parent, pes);
452                                }
453
454                                // This is a hack for BioPAX graph. Equivalence relations do not link members and
455                                // complexes because members cannot be addressed. Below call makes sure that if the
456                                // source node has a generic parents or children and they appear in a complex, we
457                                // include the complex in the sources.
458                                addEquivalentsComplexes(cpx, pes);
459                        }
460                }
461                else if (element instanceof PhysicalEntity)
462                {
463                        PhysicalEntity pe = (PhysicalEntity) element;
464                        if (!pes.contains(pe))
465                        {
466                                pes.add(pe);
467        
468                                for (Complex cmp : pe.getComponentOf())
469                                {
470                                        getRelatedPhysicalEntities(cmp, pes);
471                                }
472
473                                // This is a hack for BioPAX graph. Equivalence relations do not link members and
474                                // complexes because members cannot be addressed. Below call makes sure that if the
475                                // source node has a generic parents or children and they appear in a complex, we
476                                // include the complex in the sources.
477                                addEquivalentsComplexes(pe, pes);
478                        }
479                }
480                else if (element instanceof Xref)
481                {
482                        for (XReferrable xrable : ((Xref) element).getXrefOf())
483                        {
484                                getRelatedPhysicalEntities(xrable, pes);
485                        }
486                }
487                else if (element instanceof EntityReference)
488                {
489                        EntityReference er = (EntityReference) element;
490
491                        for (SimplePhysicalEntity spe : er.getEntityReferenceOf())
492                        {
493                                getRelatedPhysicalEntities(spe, pes);
494                        }
495
496                        for (EntityReference parentER : er.getMemberEntityReferenceOf())
497                        {
498                                getRelatedPhysicalEntities(parentER, pes);
499                        }
500                }
501
502                return pes;
503        }
504
505        /**
506         * Adds equivalents and parent complexes of the given PhysicalEntity to the parameter set.
507         * @param pe The PhysicalEntity to add its equivalents and complexes
508         * @param pes Set to collect equivalents and complexes
509         */
510        private static void addEquivalentsComplexes(PhysicalEntity pe, Set<PhysicalEntity> pes)
511        {
512                addEquivalentsComplexes(pe, true, pes);
513                addEquivalentsComplexes(pe, false, pes);
514        }
515
516        /**
517         * Adds equivalents and parent complexes of the given PhysicalEntity to the parameter set. This
518         * method traverses homologies only to one direction (either towards parents or to the
519         * children).
520         * @param pe The PhysicalEntity to add its equivalents and complexes
521         * @param outer Give true if towards parents, false if to the children
522         * @param pes Set to collect equivalents and complexes
523         */
524        private static void addEquivalentsComplexes(PhysicalEntity pe, boolean outer,
525                Set<PhysicalEntity> pes)
526        {
527                Set<PhysicalEntity> set = outer ? 
528                        pe.getMemberPhysicalEntityOf() : pe.getMemberPhysicalEntity();
529
530                for (PhysicalEntity related : set)
531                {
532                        for (Complex cmp : related.getComponentOf())
533                        {
534                                getRelatedPhysicalEntities(cmp, pes);
535                        }
536
537                        addEquivalentsComplexes(related, outer, pes);
538                }
539        }
540
541        /**
542         * Extracts the querible interactions from the elements.
543         *
544         * @param elements BioPAX elements to search
545         * @param graph graph model
546         * @return Querible Interactions (nodes)
547         */
548        public static Set<Node> getSeedInteractions(Collection<BioPAXElement> elements, Graph graph)
549        {
550                Set<Node> nodes = new HashSet<Node>();
551
552                for (BioPAXElement ele : elements)
553                {
554                        if (ele instanceof Conversion || ele instanceof TemplateReaction ||
555                                ele instanceof Control)
556                        {
557                                GraphObject go = graph.getGraphObject(ele);
558
559                                if (go instanceof Node)
560                                {
561                                        nodes.add((Node) go);
562                                }
563                        }
564                }
565                return nodes;
566        }
567}