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}