View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.context;
21  
22  import com.google.common.base.Preconditions;
23  import com.google.common.collect.ImmutableList;
24  import org.apache.commons.lang3.tuple.Pair;
25  import org.grouplens.grapht.reflect.InjectionPoint;
26  import org.grouplens.grapht.reflect.Satisfaction;
27  import org.grouplens.grapht.solver.InjectionContext;
28  import org.grouplens.grapht.util.AbstractChain;
29  
30  import javax.annotation.Nullable;
31  import java.io.Serializable;
32  import java.util.Collections;
33  import java.util.List;
34  
35  /**
36   * A regular pattern matching contexts.
37   *
38   * @since 0.7
39   * @author <a href="http://www.grouplens.org">GroupLens Research</a>
40   */
41  public class ContextPattern implements ContextMatcher, Serializable {
42      private static final long serialVersionUID = 1L;
43  
44      private final List<Element> tokenChain;
45  
46      private ContextPattern() {
47          tokenChain = Collections.emptyList();
48      }
49  
50      private ContextPattern(List<Element> tokens) {
51          tokenChain = ImmutableList.copyOf(tokens);
52      }
53  
54      /**
55       * Create an empty context pattern.
56       * @return A context pattern matching only the empty context.
57       */
58      public static ContextPattern empty() {
59          return new ContextPattern();
60      }
61  
62      /**
63       * Create a context pattern matching any context.
64       * @return A context pattern matching any context.
65       */
66      public static ContextPattern any() {
67          return empty().appendDotStar();
68      }
69  
70      /**
71       * Create a context matcher matching any context with the specified subsequence.
72       *
73       * @param types The subsequence to match.
74       * @return A context matcher that matches any context of which {@code types} (with the default
75       *         qualifier matchers) is a subsequence.
76       */
77      public static ContextPattern subsequence(Class<?>... types) {
78          ContextPattern pat = any();
79          for (Class<?> type: types) {
80              pat = pat.append(ContextElements.matchType(type), Multiplicity.ONE)
81                       .appendDotStar();
82          }
83          return pat;
84      }
85  
86      /**
87       * Create a context matcher matching any context with a subequence matching the provided matchers.
88       *
89       * @param matchers The matchers to match a subsequence.
90       * @return A context matcher that matches any context of which {@code types} (with the default
91       *         qualifier matchers) is a subsequence.
92       */
93      public static ContextPattern subsequence(ContextElementMatcher... matchers) {
94          ContextPattern pat = any();
95          for (ContextElementMatcher matcher: matchers) {
96              pat = pat.append(matcher, Multiplicity.ONE)
97                       .appendDotStar();
98          }
99          return pat;
100     }
101 
102     /**
103      * Append an element to this pattern.  The pattern is not modified; rather, a new pattern
104      * extended with the new matching element is returned.
105      * @param match The element matcher.
106      * @param mult The multiplicity of this element.
107      * @return This pattern extended by the new element.
108      */
109     public ContextPattern append(ContextElementMatcher match, Multiplicity mult) {
110         Element elem = new Element(match, mult);
111         List<Element> extended = ImmutableList.<Element>builder()
112                                               .addAll(tokenChain)
113                                               .add(elem)
114                                               .build();
115         return new ContextPattern(extended);
116     }
117 
118     /**
119      * Append an element to this pattern with multiplicity {@link Multiplicity#ONE}.
120      * @param match The element matcher.
121      * @return This pattern extended by the new element.
122      */
123     public ContextPattern append(ContextElementMatcher match) {
124         return append(match, Multiplicity.ONE);
125     }
126 
127     /**
128      * Append a type element to this pattern with multiplicity {@link Multiplicity#ONE}.
129      * @param type The type to match, passed to {@link ContextElements#matchType(Class)}.
130      * @return This pattern extended by the new element.
131      */
132     public ContextPattern append(Class<?> type) {
133         return append(ContextElements.matchType(type));
134     }
135 
136     /**
137      * Append a context pattern to this pattern.
138      * @param toAppend The pattern to append.
139      * @return The concatenation of this pattern and {@code toAppend}.
140      */
141     public ContextPattern append(ContextPattern toAppend) {
142         ContextPattern pat = this;
143         for (Element elem: toAppend.tokenChain) {
144             pat = pat.append(elem.getMatcher(), elem.getMultiplicity());
145         }
146         return pat;
147     }
148 
149     /**
150      * Return a pattern matching any context of which the current pattern matches a prefix.  This
151      * is accomplished by adding the {@code .*} regular expression token.
152      * @return The new pattern.
153      */
154     public ContextPattern appendDotStar() {
155         if (!tokenChain.isEmpty()) {
156             Element elem = tokenChain.get(tokenChain.size() - 1);
157             if (elem.getMatcher().equals(ContextElements.matchAny()) && elem.getMultiplicity().equals(Multiplicity.ZERO_OR_MORE)) {
158                 return this;
159             }
160         }
161 
162         return append(ContextElements.matchAny(), Multiplicity.ZERO_OR_MORE);
163     }
164 
165     @Override
166     public ContextMatch matches(InjectionContext context) {
167         List<MatchElement> result = recursiveMatch(tokenChain, ImmutableList.copyOf(context));
168         if (result == null) {
169             return null;
170         } else {
171             return ContextMatch.create(result);
172         }
173     }
174 
175     /**
176      * Recursive matching routine.  Matches the pattern via backtracking.  Returns the matched
177      * elements.
178      *
179      * @param pattern The pattern.
180      * @param context The context.
181      * @return The chain of match elements (in reverse order).
182      */
183     private List<MatchElement> recursiveMatch(List<Element> pattern, List<Pair<Satisfaction, InjectionPoint>> context) {
184         if (pattern.isEmpty()) {
185             if (context.isEmpty()) {
186                 return Collections.emptyList();
187             } else {
188                 return null;
189             }
190         }
191 
192         Element element = listHead(pattern);
193 
194         // matching against the empty string with a nonempty pattern
195         if (context.isEmpty()) {
196             if (element.getMultiplicity().isOptional()) {
197                 return recursiveMatch(pattern.subList(1, pattern.size()), context);
198             } else {
199                 return null;
200             }
201         }
202         // non-empty pattern, non-empty context, go
203         Pair<Satisfaction,InjectionPoint> ctxElem = listHead(context);
204         MatchElement match = element.getMatcher().apply(ctxElem);
205         if (match == null) {
206             // no match, what do we do?
207             if (element.getMultiplicity().isOptional()) {
208                 // skip this element, keep going
209                 return recursiveMatch(listTail(pattern), context);
210             } else {
211                 // oops, we must match
212                 return null;
213             }
214         } else {
215             // we have a match, try recursion
216             List<Element> nextPat = pattern;
217             if (element.getMultiplicity().isConsumed()) {
218                 nextPat = listTail(nextPat);
219             }
220             List<MatchElement> result = recursiveMatch(nextPat, listTail(context));
221             if (result == null && element.getMultiplicity().isOptional()) {
222                 // recursive match failed, but element is optional. Try again without it.
223                 return recursiveMatch(listTail(pattern), context);
224             }
225             if (result != null) {
226                 return ImmutableList.<MatchElement>builder()
227                                     .add(match)
228                                     .addAll(result)
229                                     .build();
230             } else {
231                 return result;
232             }
233         }
234     }
235 
236     @Override
237     public String toString() {
238         StringBuilder sb = new StringBuilder();
239         sb.append("ContextPattern(");
240         for (Element tok: tokenChain) {
241             sb.append(tok);
242         }
243         return sb.append(")").toString();
244     }
245 
246     @Override
247     public boolean equals(Object o) {
248         if (this == o) return true;
249         if (o == null || getClass() != o.getClass()) return false;
250 
251         ContextPattern that = (ContextPattern) o;
252 
253         if (!tokenChain.equals(that.tokenChain)) return false;
254 
255         return true;
256     }
257 
258     @Override
259     public int hashCode() {
260         return tokenChain.hashCode();
261     }
262 
263     /**
264      * An element of the context pattern.
265      */
266     public static class Element implements Serializable {
267         private static final long serialVersionUID = 1L;
268 
269         private final ContextElementMatcher matcher;
270         private final Multiplicity multiplicity;
271 
272         public Element(ContextElementMatcher mat, Multiplicity mult) {
273             matcher = mat;
274             multiplicity = mult;
275         }
276 
277         public ContextElementMatcher getMatcher() {
278             return matcher;
279         }
280 
281         public Multiplicity getMultiplicity() {
282             return multiplicity;
283         }
284 
285         @Override
286         public boolean equals(Object o) {
287             if (this == o) return true;
288             if (o == null || getClass() != o.getClass()) return false;
289 
290             Element element = (Element) o;
291 
292             if (!matcher.equals(element.matcher)) return false;
293             if (multiplicity != element.multiplicity) return false;
294 
295             return true;
296         }
297 
298         @Override
299         public int hashCode() {
300             int result = matcher.hashCode();
301             result = 31 * result + multiplicity.hashCode();
302             return result;
303         }
304 
305         @Override
306         public String toString() {
307             StringBuilder sb = new StringBuilder();
308             sb.append(matcher);
309             switch (multiplicity) {
310             case ONE:
311                 break;
312             case ZERO_OR_MORE:
313                 sb.append("*");
314                 break;
315             default:
316                 throw new IllegalStateException("unknown multiplicity");
317             }
318             return sb.toString();
319         }
320     }
321 
322     private static <E> E listHead(List<E> lst) {
323         Preconditions.checkArgument(!lst.isEmpty(), "list cannot be empty");
324         return lst.get(0);
325     }
326 
327     private static <E> List<E> listTail(List<E> lst) {
328         Preconditions.checkArgument(!lst.isEmpty(), "list cannot be empty");
329         return lst.subList(1, lst.size());
330     }
331 }