1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
37
38
39
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
56
57
58 public static ContextPattern empty() {
59 return new ContextPattern();
60 }
61
62
63
64
65
66 public static ContextPattern any() {
67 return empty().appendDotStar();
68 }
69
70
71
72
73
74
75
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
88
89
90
91
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
104
105
106
107
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
120
121
122
123 public ContextPattern append(ContextElementMatcher match) {
124 return append(match, Multiplicity.ONE);
125 }
126
127
128
129
130
131
132 public ContextPattern append(Class<?> type) {
133 return append(ContextElements.matchType(type));
134 }
135
136
137
138
139
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
151
152
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
177
178
179
180
181
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
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
203 Pair<Satisfaction,InjectionPoint> ctxElem = listHead(context);
204 MatchElement match = element.getMatcher().apply(ctxElem);
205 if (match == null) {
206
207 if (element.getMultiplicity().isOptional()) {
208
209 return recursiveMatch(listTail(pattern), context);
210 } else {
211
212 return null;
213 }
214 } else {
215
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
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
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 }