001package org.biopax.paxtools.controller;
002
003import java.util.HashSet;
004import java.util.Set;
005import java.util.concurrent.atomic.AtomicBoolean;
006
007import org.apache.commons.lang.ArrayUtils;
008import org.biopax.paxtools.model.BioPAXElement;
009import org.biopax.paxtools.model.Model;
010import org.biopax.paxtools.model.level3.Pathway;
011import org.biopax.paxtools.util.Filter;
012
013/**
014 * This class is used to fetch an element (traverse it to obtain
015 * its dependent elements) and to add this element into a model
016 * using the visitor and traverse functions.
017 *
018 * Must be thread safe if the property filters and {@link EditorMap} 
019 * passed to the constructor are safe.
020 * 
021 * @see org.biopax.paxtools.controller.Visitor
022 * @see org.biopax.paxtools.controller.Traverser
023 *
024 */
025public class Fetcher {
026    
027        private final EditorMap editorMap;
028    private final Filter<PropertyEditor>[] filters;
029
030        private boolean skipSubPathways;
031
032        /**
033         * This property filter can be used to ignore 'nextStep' ('NEXT-STEP' in L2) 
034         * property when fetching a sub-graph of child biopax elements, because
035         * using this property can eventually lead outside current pathway context
036         * into peer pathways, etc. (in practice, for a pathway, all its child elements 
037         * can be reached via 'pathwayComponent', 'pathwayOrder/stepProcess', 
038         * 'pathwayOrder/stepConvertion' etc. properties; so ignoring the 'nextStep' usually OK).
039         * So, this is recommended filter for common BioPAX fetching/traversal tasks, such as
040         * to find all proteins or xrefs within a pathway and its sub-pathways, etc.
041         */
042        @SuppressWarnings("rawtypes")
043        public static final Filter<PropertyEditor> nextStepFilter = new Filter<PropertyEditor>()
044        {
045                public boolean filter(PropertyEditor editor)
046                {
047                        return !editor.getProperty().equals("nextStep") && !editor.getProperty().equals("NEXT-STEP");
048                }
049        };
050        
051
052        /**
053         * A property filter to ignore 'evidence' ('EVIDENCE' in L2) property
054         * (it can eventually lead to other organism, experimental entities)
055         */
056        @SuppressWarnings("rawtypes")
057        public static final Filter<PropertyEditor> evidenceFilter = new Filter<PropertyEditor>()
058        {
059                public boolean filter(PropertyEditor editor)
060                {
061                        return !editor.getProperty().equals("evidence") && !editor.getProperty().equals("EVIDENCE");
062                }
063        };
064
065
066        /**
067         * A property filter to visit only biopax object type properties.
068         */
069        public static final Filter<PropertyEditor> objectPropertiesOnlyFilter = new Filter<PropertyEditor>()
070        {
071                public boolean filter(PropertyEditor editor)
072                {
073                        return (editor instanceof ObjectPropertyEditor);
074                }
075        };
076
077        
078    /**
079     * Constructor.
080     * 
081     * @param editorMap BioPAX property editors map implementation
082     * @param filters optional, biopax object property filters 
083     *        to skip traversing/visiting into some object property values
084         *        (the default 'object properties only' filter to is always enabled).
085     */
086        public Fetcher(EditorMap editorMap, Filter<PropertyEditor>... filters) {
087        this.editorMap = editorMap;
088        this.filters = (Filter<PropertyEditor>[])ArrayUtils.add(filters, objectPropertiesOnlyFilter);
089                this.skipSubPathways = false;
090    }
091
092
093        /**
094         * Use this property to optionally
095         * skip (if true) traversing into sub-pathways;
096         * i.e., when a biopax property, such as pathwayComponent
097         * or controlled, value is a Pathway.
098         *
099         * @param skipSubPathways true/false
100         */
101        public void setSkipSubPathways(boolean skipSubPathways) {
102                this.skipSubPathways = skipSubPathways;
103        }
104
105        public boolean isSkipSubPathways() {
106                return skipSubPathways;
107        }
108
109        /**
110     * Adds the element and all its children
111         * (found via traversing into object properties that
112         * pass all the filters defined in the Constructor, and
113         * also taking #isSkipSubPathways into account)
114         * to the target model.
115     *
116     * This method fails if there are different child objects
117     * with the same ID, because normally a good (self-consistent) 
118     * model does not contain duplicate BioPAX elements. Consider
119     * using {@link #fetch(BioPAXElement)} method instead if you 
120     * want to get all the child elements anyway.
121     *
122     * @param element the BioPAX element to be added into the model
123     * @param model model into which elements will be added
124     */
125    public void fetch(final BioPAXElement element, final Model model)
126        {
127        if(!model.containsID(element.getRDFId()))
128                model.add(element);
129        
130        Set<BioPAXElement> children = fetch(element);
131        
132        for(BioPAXElement e : children) {
133                        if (!model.containsID(e.getRDFId())) {
134                                model.add(e);
135                        } else if (!model.contains(e)) {
136                                throw new AssertionError("fetch(bioPAXElement, model): found different child objects " +
137                                                "with the same URI: " + e.getRDFId() +
138                                                "(replace/merge, or use fetch(bioPAXElement) instead!)");
139                        }
140                }
141        }
142 
143    
144    /**
145     * Recursively finds and collects all child objects,
146         * while escaping possible infinite loops.
147     * 
148     * This method can eventually return
149     * different objects with the same URI if these
150     * are present among child elements.
151     * 
152     * @param element to traverse into
153     * @return a set of child biopax objects
154     */
155    public Set<BioPAXElement> fetch(final BioPAXElement element) {
156        return fetch(element, BioPAXElement.class);
157    }
158    
159    
160    /**
161     * Recursively collects unique child objects from
162         * BioPAX object type properties that pass
163         * all the filters (as set via Constructor).
164         *
165         * The #isSkipSubPathways flag is ignored.
166     * 
167     * Note: this method might return
168     * different objects with the same URI if such
169     * are present among the child elements for some reason
170         * (in a self-integral BioPAX Model, this should never be allowed,
171         * but can happen as the result of cloning/replacing in some other methods).
172     * 
173     * @param bpe biopax object to traverse into properties of
174     * @param depth positive int.; 1 means - get only direct children, 2 - include children of children, etc.;
175     * @return set of child objects
176         * @throws IllegalArgumentException when depth is less or equals 0
177     */
178    public Set<BioPAXElement> fetch(final BioPAXElement bpe, int depth)
179        {
180                //a sanity check
181                if(depth <= 0) {
182                        throw new IllegalArgumentException("fetch(..), not a positive 'depth':" + depth);
183                }
184
185                final Set<BioPAXElement> children = new HashSet<BioPAXElement>();
186
187                //create a simple traverser to collect direct child elements
188                Traverser traverser = new Traverser(SimpleEditorMap.L3,
189                                new Visitor() {
190                                        @Override
191                                        public void visit(BioPAXElement domain, Object range, Model model, PropertyEditor<?, ?> editor) {
192                                                        children.add((BioPAXElement) range);
193                                        }
194                                }, filters);
195                //run
196                traverser.traverse(bpe, null);
197
198                if(!children.isEmpty() && --depth > 0) {
199                        for (BioPAXElement element : new HashSet<BioPAXElement>(children)) {
200                                Set<BioPAXElement> nextLevelElements = fetch(element, depth); //recursion goes on
201                                children.addAll(nextLevelElements);
202                        }
203                }
204
205                //remove itself (if added due to tricky loops in the model...)
206                children.remove(bpe);
207
208                return children;
209        }
210
211    
212    /**
213     * Goes over object type biopax properties to collect nested objects
214         * (using only properties that pass all the filters set in Constructor,
215         * and taking #isSkipSubPathways into account)
216         * of the given biopax element, its children, etc.
217         * (it also escapes any infinite semantic loops in the biopax model)
218         * It saves only biopax objects of the given type, incl. sub-types.
219     * 
220     * Note: this method can eventually return
221     * different objects with the same URI if these
222     * are present among child elements.
223     * 
224     * @param element to fetch child objects from
225     * @param filterByType biopax type filter
226     * @param <T> biopax type
227     * @return set of biopax objects
228     */
229    public <T extends BioPAXElement> Set<T> fetch(final BioPAXElement element, final Class<T> filterByType)
230        {
231        final Set<T> children = new HashSet<T>();
232
233                AbstractTraverser traverser = new AbstractTraverser(editorMap, filters) {
234            /*
235             * Adds the BioPAX element into the model and traverses the element's properties
236             * to collect all dependent/nested elements.
237             */
238            protected void visit(Object range, BioPAXElement domain, Model model, PropertyEditor editor)
239                {
240                                //by design (see Constructor, filters), it'll visit only object properties.
241                                BioPAXElement bpe = (BioPAXElement) range;
242
243                                if(filterByType.isInstance(bpe)) {
244                                        children.add((T) bpe);
245                                }
246
247                                if(!(skipSubPathways && (range instanceof Pathway)))
248                                        traverse(bpe, null); //go deeper only if it's a new object
249                        }
250        };
251
252        traverser.traverse(element, null);
253        
254        return children;
255        }
256    
257    
258    /**
259     * Iterates over child objects of the given biopax element, 
260     * using BioPAX object-type properties, until the element 
261     * with specified URI and class (including its sub-classes). 
262     * is found.
263     * 
264     * @param root biopax element to process
265     * @param uri URI to match
266     * @param type class to match
267     * 
268     * @return true if the match found; false - otherwise
269     */
270    public boolean subgraphContains(final BioPAXElement root, final String uri, 
271                final Class<? extends BioPAXElement> type) {
272        
273        final AtomicBoolean found = new AtomicBoolean(false);
274        
275        Traverser traverser = new AbstractTraverser(editorMap, filters) {  
276                
277            @Override
278            protected void visit(Object range, BioPAXElement domain, Model model, PropertyEditor editor)
279                {
280                        if (range instanceof BioPAXElement && !found.get())
281                        {
282                                if( ((BioPAXElement) range).getRDFId().equals(uri) )
283                                        found.set(true); //set global flag; done.
284                                else
285                                                if(!(skipSubPathways && (range instanceof Pathway)))
286                                                traverse((BioPAXElement)range, model);
287                        }
288                }
289        };
290
291        traverser.traverse(root, null);
292        
293        return found.get();
294        }
295}