Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
FeatureMatcherSet | 37 | 87 | 34 |
1 | /* | |
2 | * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) | |
3 | * Copyright (C) $$Year-Rel$$ The Jalview Authors | |
4 | * | |
5 | * This file is part of Jalview. | |
6 | * | |
7 | * Jalview is free software: you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation, either version 3 | |
10 | * of the License, or (at your option) any later version. | |
11 | * | |
12 | * Jalview is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty | |
14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
15 | * PURPOSE. See the GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with Jalview. If not, see <http://www.gnu.org/licenses/>. | |
19 | * The Jalview Authors are detailed in the 'AUTHORS' file. | |
20 | */ | |
21 | package jalview.datamodel.features; | |
22 | ||
23 | import java.util.Locale; | |
24 | ||
25 | import jalview.datamodel.SequenceFeature; | |
26 | import jalview.util.MessageManager; | |
27 | ||
28 | import java.util.ArrayList; | |
29 | import java.util.List; | |
30 | ||
31 | /** | |
32 | * A class that models one or more match conditions, which may be combined with | |
33 | * AND or OR (but not a mixture) | |
34 | * | |
35 | * @author gmcarstairs | |
36 | */ | |
37 | public class FeatureMatcherSet implements FeatureMatcherSetI | |
38 | { | |
39 | private static final String OR = "OR"; | |
40 | ||
41 | private static final String AND = "AND"; | |
42 | ||
43 | private static final String SPACE = " "; | |
44 | ||
45 | private static final String CLOSE_BRACKET = ")"; | |
46 | ||
47 | private static final String OPEN_BRACKET = "("; | |
48 | ||
49 | private static final String OR_I18N = MessageManager | |
50 | .getString("label.or"); | |
51 | ||
52 | private static final String AND_18N = MessageManager | |
53 | .getString("label.and"); | |
54 | ||
55 | List<FeatureMatcherI> matchConditions; | |
56 | ||
57 | boolean andConditions; | |
58 | ||
59 | /** | |
60 | * A factory constructor that converts a stringified object (as output by | |
61 | * toStableString) to an object instance. | |
62 | * | |
63 | * Format: | |
64 | * <ul> | |
65 | * <li>(condition1) AND (condition2) AND (condition3)</li> | |
66 | * <li>or</li> | |
67 | * <li>(condition1) OR (condition2) OR (condition3)</li> | |
68 | * </ul> | |
69 | * where OR and AND are not case-sensitive, and may not be mixed. Brackets are | |
70 | * optional if there is only one condition. | |
71 | * | |
72 | * @param descriptor | |
73 | * @return | |
74 | * @see FeatureMatcher#fromString(String) | |
75 | */ | |
76 | 18 | public static FeatureMatcherSet fromString(final String descriptor) |
77 | { | |
78 | 18 | String invalid = "Invalid descriptor: " + descriptor; |
79 | 18 | boolean firstCondition = true; |
80 | 18 | FeatureMatcherSet result = new FeatureMatcherSet(); |
81 | ||
82 | 18 | String leftToParse = descriptor.trim(); |
83 | ||
84 | 44 | while (leftToParse.length() > 0) |
85 | { | |
86 | /* | |
87 | * inspect AND or OR condition, check not mixed | |
88 | */ | |
89 | 32 | boolean and = true; |
90 | 32 | if (!firstCondition) |
91 | { | |
92 | 14 | int spacePos = leftToParse.indexOf(SPACE); |
93 | 14 | if (spacePos == -1) |
94 | { | |
95 | // trailing junk after a match condition | |
96 | 0 | jalview.bin.Console.errPrintln(invalid); |
97 | 0 | return null; |
98 | } | |
99 | 14 | String conjunction = leftToParse.substring(0, spacePos); |
100 | 14 | leftToParse = leftToParse.substring(spacePos + 1).trim(); |
101 | 14 | if (conjunction.equalsIgnoreCase(AND)) |
102 | { | |
103 | 5 | and = true; |
104 | } | |
105 | 9 | else if (conjunction.equalsIgnoreCase(OR)) |
106 | { | |
107 | 8 | and = false; |
108 | } | |
109 | else | |
110 | { | |
111 | // not an AND or an OR - invalid | |
112 | 1 | jalview.bin.Console.errPrintln(invalid); |
113 | 1 | return null; |
114 | } | |
115 | } | |
116 | ||
117 | /* | |
118 | * now extract the next condition and AND or OR it | |
119 | */ | |
120 | 31 | String nextCondition = leftToParse; |
121 | 31 | if (leftToParse.startsWith(OPEN_BRACKET)) |
122 | { | |
123 | 26 | int closePos = leftToParse.indexOf(CLOSE_BRACKET); |
124 | 26 | if (closePos == -1) |
125 | { | |
126 | 0 | jalview.bin.Console.errPrintln(invalid); |
127 | 0 | return null; |
128 | } | |
129 | 26 | nextCondition = leftToParse.substring(1, closePos); |
130 | 26 | leftToParse = leftToParse.substring(closePos + 1).trim(); |
131 | } | |
132 | else | |
133 | { | |
134 | 5 | leftToParse = ""; |
135 | } | |
136 | ||
137 | 31 | FeatureMatcher fm = FeatureMatcher.fromString(nextCondition); |
138 | 31 | if (fm == null) |
139 | { | |
140 | 3 | jalview.bin.Console.errPrintln(invalid); |
141 | 3 | return null; |
142 | } | |
143 | 28 | try |
144 | { | |
145 | 28 | if (and) |
146 | { | |
147 | 20 | result.and(fm); |
148 | } | |
149 | else | |
150 | { | |
151 | 8 | result.or(fm); |
152 | } | |
153 | 26 | firstCondition = false; |
154 | } catch (IllegalStateException e) | |
155 | { | |
156 | // thrown if OR and AND are mixed | |
157 | 2 | jalview.bin.Console.errPrintln(invalid); |
158 | 2 | return null; |
159 | } | |
160 | ||
161 | } | |
162 | 12 | return result; |
163 | } | |
164 | ||
165 | /** | |
166 | * Constructor | |
167 | */ | |
168 | 57 | public FeatureMatcherSet() |
169 | { | |
170 | 57 | matchConditions = new ArrayList<>(); |
171 | } | |
172 | ||
173 | 47 | @Override |
174 | public boolean matches(SequenceFeature feature) | |
175 | { | |
176 | /* | |
177 | * no conditions matches anything | |
178 | */ | |
179 | 47 | if (matchConditions.isEmpty()) |
180 | { | |
181 | 2 | return true; |
182 | } | |
183 | ||
184 | /* | |
185 | * AND until failure | |
186 | */ | |
187 | 45 | if (andConditions) |
188 | { | |
189 | 37 | for (FeatureMatcherI m : matchConditions) |
190 | { | |
191 | 39 | if (!m.matches(feature)) |
192 | { | |
193 | 25 | return false; |
194 | } | |
195 | } | |
196 | 12 | return true; |
197 | } | |
198 | ||
199 | /* | |
200 | * OR until match | |
201 | */ | |
202 | 8 | for (FeatureMatcherI m : matchConditions) |
203 | { | |
204 | 14 | if (m.matches(feature)) |
205 | { | |
206 | 5 | return true; |
207 | } | |
208 | } | |
209 | 3 | return false; |
210 | } | |
211 | ||
212 | 63 | @Override |
213 | public void and(FeatureMatcherI m) | |
214 | { | |
215 | 63 | if (!andConditions && matchConditions.size() > 1) |
216 | { | |
217 | 4 | throw new IllegalStateException("Can't add an AND to OR conditions"); |
218 | } | |
219 | 59 | matchConditions.add(m); |
220 | 59 | andConditions = true; |
221 | } | |
222 | ||
223 | 28 | @Override |
224 | public void or(FeatureMatcherI m) | |
225 | { | |
226 | 28 | if (andConditions && matchConditions.size() > 1) |
227 | { | |
228 | 1 | throw new IllegalStateException("Can't add an OR to AND conditions"); |
229 | } | |
230 | 27 | matchConditions.add(m); |
231 | 27 | andConditions = false; |
232 | } | |
233 | ||
234 | 6 | @Override |
235 | public boolean isAnded() | |
236 | { | |
237 | 6 | return andConditions; |
238 | } | |
239 | ||
240 | 11 | @Override |
241 | public Iterable<FeatureMatcherI> getMatchers() | |
242 | { | |
243 | 11 | return matchConditions; |
244 | } | |
245 | ||
246 | /** | |
247 | * Answers a string representation of this object suitable for display, and | |
248 | * possibly internationalized. The format is not guaranteed stable and may | |
249 | * change in future. | |
250 | */ | |
251 | 6 | @Override |
252 | public String toString() | |
253 | { | |
254 | 6 | StringBuilder sb = new StringBuilder(); |
255 | 6 | boolean first = true; |
256 | 6 | boolean multiple = matchConditions.size() > 1; |
257 | 6 | for (FeatureMatcherI matcher : matchConditions) |
258 | { | |
259 | 6 | if (!first) |
260 | { | |
261 | 2 | String joiner = andConditions ? AND_18N : OR_I18N; |
262 | 2 | sb.append(SPACE).append(joiner.toLowerCase(Locale.ROOT)) |
263 | .append(SPACE); | |
264 | } | |
265 | 6 | first = false; |
266 | 6 | if (multiple) |
267 | { | |
268 | 4 | sb.append(OPEN_BRACKET).append(matcher.toString()) |
269 | .append(CLOSE_BRACKET); | |
270 | } | |
271 | else | |
272 | { | |
273 | 2 | sb.append(matcher.toString()); |
274 | } | |
275 | } | |
276 | 6 | return sb.toString(); |
277 | } | |
278 | ||
279 | 34 | @Override |
280 | public boolean isEmpty() | |
281 | { | |
282 | 34 | return matchConditions == null || matchConditions.isEmpty(); |
283 | } | |
284 | ||
285 | /** | |
286 | * {@inheritDoc} The output of this method should be parseable by method | |
287 | * <code>fromString<code> to restore the original object. | |
288 | */ | |
289 | 24 | @Override |
290 | public String toStableString() | |
291 | { | |
292 | 24 | StringBuilder sb = new StringBuilder(); |
293 | 24 | boolean moreThanOne = matchConditions.size() > 1; |
294 | 24 | boolean first = true; |
295 | ||
296 | 24 | for (FeatureMatcherI matcher : matchConditions) |
297 | { | |
298 | 38 | if (!first) |
299 | { | |
300 | 16 | String joiner = andConditions ? AND : OR; |
301 | 16 | sb.append(SPACE).append(joiner).append(SPACE); |
302 | } | |
303 | 38 | first = false; |
304 | 38 | if (moreThanOne) |
305 | { | |
306 | 30 | sb.append(OPEN_BRACKET).append(matcher.toStableString()) |
307 | .append(CLOSE_BRACKET); | |
308 | } | |
309 | else | |
310 | { | |
311 | 8 | sb.append(matcher.toStableString()); |
312 | } | |
313 | } | |
314 | 24 | return sb.toString(); |
315 | } | |
316 | ||
317 | } |