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}