001package org.biopax.paxtools.controller; 002 003 004import org.apache.commons.logging.Log; 005import org.apache.commons.logging.LogFactory; 006import org.biopax.paxtools.model.BioPAXElement; 007import org.biopax.paxtools.model.BioPAXLevel; 008import org.biopax.paxtools.model.Model; 009import org.biopax.paxtools.util.IllegalBioPAXArgumentException; 010 011import java.util.*; 012 013/** 014 * This class is a composite property accessor that allows users to chain multiple 015 * property accessors to define paths in the BioPAX object graph. 016 * 017 * The path can be defined by either explicitly providing a set of property accessors or 018 * by an xPath like syntax. 019 * 020 * The path can contain transitive and or restricted sub paths: 021 * A transitive sub-path is traversed recursively as many times as possible. e.g. Complex/component* will 022 * return all components of the complex and the components of the components if those components 023 * are complex recursively. 024 * 025 * A restricted subpath is restricted with a class and only follows through the domains that are 026 * assignable from the the restriction class. e.g. Control/controlled:Pathway/name will only return the 027 * names of the pathways that are being controlled, but not interactions. 028 */ 029public class PathAccessor extends PropertyAccessorAdapter<BioPAXElement, Object> 030{ 031 032 List<PropertyAccessor<? extends BioPAXElement, ?>> accessors; 033 034 Class<? extends BioPAXElement> domain; 035 036 List<Class<? extends BioPAXElement>> domainOrder = new ArrayList<Class<? extends BioPAXElement>>(); 037 038 public static final Log log = LogFactory.getLog(PathAccessor.class); 039 040 BioPAXLevel level; 041 042 /** 043 * Constructor for defining the access path with a list of accessors. All of the accessors must be of the same 044 * level. In the case one accessor's range can not be assigned by the domain of the next( broken path), 045 * this accessor will return an empty set. 046 * @param objectAccessors A list of object accessors. 047 * @param level biopax level 048 */ 049 public PathAccessor(List<PropertyAccessor<? extends BioPAXElement, ?>> objectAccessors, BioPAXLevel level) 050 { 051 super(BioPAXElement.class, Object.class, true); 052 this.accessors = objectAccessors; 053 this.domain = objectAccessors.get(0).getDomain(); 054 this.level = level; 055 } 056 057 058 /** 059 * Constructor for defining the access path via a XPath like string. 060 * @param path The string defining the path. The following operators can be used: 061 * <ul> 062 * <li>The first token is the class name of the starting domain.</li> 063 * <li>The next tokens are names of the properties, separated by "/"</li> 064 * <li>A property step can be restricted to a domain by defining the restriction class with ":" after the 065 * property </li> 066 * <li>A property step can be declared transitive by adding a "*" at the end. </li> 067 * <li>Two accessors can be joined by "|" (Currently not implemented)</li> 068 * <li>A subpath can be defined by a substring enclosed in "(" ")"</li> 069 * <li>Subpaths can also be restricted or made transitive</li> 070 * </ul> 071 * @param level BioPAX level that this path is defined in. 072 */ 073 public PathAccessor(String path, BioPAXLevel level) 074 { 075 super(BioPAXElement.class, Object.class, true); 076 this.level = level; 077 parsePath(path, 0); 078 } 079 080 private PathAccessor(StringTokenizer tk, BioPAXLevel level, Class<? extends BioPAXElement> domain, int depth) 081 { 082 super(BioPAXElement.class, Object.class, true); 083 this.domain = domain; 084 this.level = level; 085 if (iterateTheRemainingPath(depth, tk) != depth) 086 throw new IllegalBioPAXArgumentException("Unexpected element. Parentheses do not match!"); 087 } 088 089 private int parsePath(String path, int depth) 090 { 091 StringTokenizer st = new StringTokenizer(path, "/:*|()", true); 092 domain = getClass(st.nextToken()); 093 this.accessors = new ArrayList<PropertyAccessor<? extends BioPAXElement, ?>>(); 094 if (st.nextToken().equals("/")) 095 { 096 accessors.add(getStepAccessor(level, st, domain)); 097 domainOrder.add(domain); 098 } else throw new IllegalArgumentException(); 099 return iterateTheRemainingPath(depth, st); 100 } 101 102 private int iterateTheRemainingPath(int depth, StringTokenizer st) 103 { 104 Class<? extends BioPAXElement> intermediate = getNextDomain(); 105 while (st.hasMoreTokens()) 106 { 107 String s = st.nextToken(); 108 if (s.equals("(")) 109 { 110 PathAccessor subPath = new PathAccessor(st, level, intermediate, depth++); 111 accessors.add(subPath); 112 domainOrder.add(intermediate); 113 intermediate = getNextDomain(); 114 } else if (s.equals(")")) 115 { 116 return depth; 117 } else if (s.equals("/")) 118 { 119 accessors.add(getStepAccessor(level, st, intermediate)); 120 domainOrder.add(intermediate); 121 intermediate = getNextDomain(); 122 123 } else if (s.equals("*")) 124 { 125 PropertyAccessor lastAccessor = 126 accessors.remove(accessors.size() - 1); 127 accessors.add(TransitivePropertyAccessor.create(lastAccessor)); 128 129 } else if (s.equals(":")) 130 { 131 Class<? extends BioPAXElement> restricted = getClass(st.nextToken()); 132 if (restricted != null) 133 { 134 PropertyAccessor<? extends BioPAXElement, ?> lastAccessor = accessors.remove(accessors.size() - 1); 135 accessors.add(FilteredPropertyAccessor.create(lastAccessor, restricted)); 136 } 137 intermediate = restricted; 138 } else if (s.equals("|")) 139 { 140 throw new UnsupportedOperationException("Not implemented yet"); 141 } else throw new IllegalArgumentException(); 142 143 } 144 return depth; 145 } 146 147 private Class<? extends BioPAXElement> getNextDomain() 148 { 149 return accessors.isEmpty() ? domain : getLastAccessor(); 150 } 151 152 private Class<? extends BioPAXElement> getLastAccessor() 153 { 154 return (Class<? extends BioPAXElement>) accessors.get(accessors.size() - 1).getRange(); 155 } 156 157 /** 158 * This constructor defaults to BioPAX Level 3. 159 * @param path The string defining the path. The following operators can be used: 160 * <ul> 161 * <li>The first token is the class name of the starting domain.</li> 162 * <li>The next tokens are names of the properties, separated by "/"</li> 163 * <li>A property step can be restricted to a domain by defining the restriction class with ":" after the 164 * property </li> 165 * <li>A property step can be declared transitive by adding a "*" at the end. </li> 166 * <li>Two accessors can be joined by "|" (Currently not implemented)</li> 167 * <li>A subpath can be defined by a substring enclosed in "(" ")"</li> 168 * <li>Subpaths can also be restricted or made transitive</li> 169 * </ul> 170 */ 171 public PathAccessor(String path) 172 { 173 this(path, BioPAXLevel.L3); 174 } 175 176 private Class<? extends BioPAXElement> getClass(String domainstr) 177 { 178 Class<? extends BioPAXElement> domain = level.getInterfaceForName(domainstr); 179 180 if (domain == null) throw new IllegalBioPAXArgumentException( 181 "Could not parse path." + domainstr + " did not resolve to any" + "BioPAX classes in level " + level + 182 "."); 183 184 return domain; 185 } 186 187 public Set getValueFromBean(BioPAXElement bean) throws IllegalBioPAXArgumentException 188 { 189 Set<BioPAXElement> bpes = new HashSet<BioPAXElement>(); 190 bpes.add(bean); 191 return getValueFromBeans(bpes); 192 } 193 194 @Override 195 public Set getValueFromBeans(Collection<? extends BioPAXElement> beans) throws IllegalBioPAXArgumentException 196 { 197 Collection<? extends BioPAXElement> bpes = beans; 198 for (int i = 0; i < accessors.size() - 1; i++) 199 { 200 PropertyAccessor accessor = accessors.get(i); 201 if (log.isTraceEnabled()) log.trace(accessor); 202 HashSet<BioPAXElement> nextBpes = new HashSet<BioPAXElement>(); 203 for (BioPAXElement bpe : bpes) 204 { 205 if (log.isTraceEnabled()) log.trace("\t" + bpe); 206 Set valueFromBean = accessor.getValueFromBean(bpe); 207 if (valueFromBean != null || valueFromBean.isEmpty()) 208 { 209 if (log.isTraceEnabled()) log.trace("\t\tv:" + valueFromBean); 210 nextBpes.addAll(valueFromBean); 211 if (log.isTraceEnabled()) log.trace("\t\tn:" + nextBpes); 212 } 213 } 214 bpes = nextBpes; 215 } 216 HashSet values = new HashSet(); 217 PropertyAccessor lastStep = accessors.get(accessors.size() - 1); 218 Class<? extends BioPAXElement> lastDomain = domainOrder.get(domainOrder.size() - 1); 219 log.trace(lastStep); 220 for (BioPAXElement bpe : bpes) 221 { 222 if (!lastDomain.isInstance(bpe)) continue; 223 224 log.trace("\t" + bpe); 225 Set valueFromBean = lastStep.getValueFromBean(bpe); 226 if (valueFromBean != null || valueFromBean.isEmpty()) 227 { 228 values.addAll(lastStep.getValueFromBean(bpe)); 229 log.trace("\t" + values); 230 } 231 } 232 return values; 233 } 234 235 /** 236 * This method runs the path query on all the elements within the model. 237 * @param model to be queried 238 * @return a merged set of all values that is reachable by the paths starting from all applicable objects in 239 * the model. For example running ProteinReference/xref:UnificationXref on the model will get all the 240 * unification xrefs of all ProteinReferences. 241 */ 242 public Set getValueFromModel(Model model) 243 { 244 Set<? extends BioPAXElement> domains = new HashSet<BioPAXElement>(model.getObjects(this.getDomain())); 245 return getValueFromBeans(domains); 246 } 247 248 private <D extends BioPAXElement> PropertyAccessor getStepAccessor(BioPAXLevel level, StringTokenizer ct, 249 Class<D> domain) 250 { 251 String property = ct.nextToken(); 252 PropertyAccessor simple = null; 253 if (property.endsWith("Of")) 254 { 255 String forwardName = property.substring(0, property.length() - 2); 256 Set<ObjectPropertyEditor> iEds = SimpleEditorMap.get(level).getInverseEditorsOf(domain); 257 if (iEds == null) 258 { 259 throw new IllegalBioPAXArgumentException("No inverse editors defined for " + domain); 260 } 261 for (ObjectPropertyEditor ope : iEds) 262 { 263 if (ope.property.equals(forwardName)) 264 { 265 if (simple == null) simple = ope.getInverseAccessor();//TODO why not simply break, after assignment, instead using 'if'? 266 } 267 268 } 269 } else 270 { 271 simple = SimpleEditorMap.get(level).getEditorForProperty(property, domain); 272 if (simple == null) 273 { 274 Set<PropertyEditor<? extends D, ?>> subclassEditorsForProperty = 275 SimpleEditorMap.get(level).getSubclassEditorsForProperty(property, domain); 276 simple = new UnionPropertyAccessor(subclassEditorsForProperty, domain); 277 278 } 279 } 280 return simple; 281 } 282 283 @Override public boolean isUnknown(Object value) 284 { 285 if (value instanceof Set) 286 { 287 for (Object o : (Set) value) 288 { 289 if (!isSingleUnknown(o)) 290 { 291 return false; // found a "known" value 292 } 293 } 294 // empty set or all unknown 295 return true; 296 } else 297 { 298 return isSingleUnknown(value); 299 } 300 } 301 302 private boolean isSingleUnknown(Object value) 303 { 304 return value == null || BioPAXElement.UNKNOWN_DOUBLE.equals(value) || 305 BioPAXElement.UNKNOWN_FLOAT.equals(value) || BioPAXElement.UNKNOWN_INT.equals(value); 306 } 307 308 /** 309 * @param bpe BioPAXElement to be checked. 310 * @return true if bpe is an instance of the domain of this accessor. 311 */ 312 public boolean applies(BioPAXElement bpe) 313 { 314 Class domain = accessors.iterator().next().getDomain(); 315 return domain.isInstance(bpe); 316 } 317 318}