1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package ca.uhn.hl7v2.util;
28
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Properties;
34 import java.util.StringTokenizer;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37
38 import ca.uhn.hl7v2.HL7Exception;
39 import ca.uhn.hl7v2.model.Message;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 public class MessageQuery {
98
99
100
101
102
103
104
105
106 public static Result query(Message theMessage, String theQuery) {
107 Properties clauses = getClauses(theQuery);
108
109
110 StringTokenizer select = new StringTokenizer(
111 clauses.getProperty("select"), ", ", false);
112 List<String> fieldPaths = new ArrayList<>(10);
113 Map<String, Integer> names = new HashMap<>(10);
114 while (select.hasMoreTokens()) {
115 String token = select.nextToken();
116 if (token.equals("as")) {
117 if (!select.hasMoreTokens()) {
118 throw new IllegalArgumentException(
119 "Keyword 'as' must be followed by a field label");
120 }
121 names.put(select.nextToken(), fieldPaths.size() - 1);
122 } else {
123 fieldPaths.add(token);
124 }
125 }
126
127
128 StringTokenizer loop = new StringTokenizer(clauses.getProperty("loop",
129 ""), ",", false);
130 List<String> loopPoints = new ArrayList<>(10);
131 Map<String, Integer> loopPointNames = new HashMap<>(10);
132 while (loop.hasMoreTokens()) {
133 String pointDecl = loop.nextToken();
134 StringTokenizer tok = new StringTokenizer(pointDecl, "=", false);
135 String name = tok.nextToken().trim();
136 String path = tok.nextToken().trim();
137 loopPoints.add(path);
138 loopPointNames.put(name, loopPoints.size() - 1);
139 }
140
141
142
143
144
145 StringTokenizer where = new StringTokenizer(clauses.getProperty(
146 "where", ""), ",", false);
147 List<String> filters = new ArrayList<>();
148 while (where.hasMoreTokens()) {
149 filters.add(where.nextToken());
150 }
151 String[] filterPaths = new String[filters.size()];
152 String[] filterPatterns = new String[filters.size()];
153 boolean[] exactFlags = new boolean[filters.size()];
154
155 for (int i = 0; i < filters.size(); i++) {
156 exactFlags[i] = true;
157 String filter = filters.get(i);
158 String[] parts = splitFromEnd(filter, "=");
159 if (parts[1] != null) {
160 parts[1] = parts[1].substring(1);
161 } else {
162 exactFlags[i] = false;
163 parts = splitFromEnd(filter, "like");
164 parts[1] = parts[1].substring(4);
165 }
166 filterPaths[i] = parts[0].trim();
167 parts[1] = parts[1].trim();
168 filterPatterns[i] = parts[1].substring(1, parts[1].length() - 1);
169 }
170
171 return new ResultImpl(theMessage,
172 loopPoints.toArray(new String[0]), loopPointNames,
173 fieldPaths.toArray(new String[0]), names,
174 filterPaths, filterPatterns, exactFlags);
175 }
176
177 private static Properties getClauses(String theQuery) {
178 Properties clauses = new Properties();
179
180 String[] split = splitFromEnd(theQuery, "where ");
181 setClause(clauses, "where", split[1]);
182
183 split = splitFromEnd(split[0], "loop ");
184 setClause(clauses, "loop", split[1]);
185 setClause(clauses, "select", split[0]);
186
187 if (clauses.getProperty("where", "").contains("loop ")) {
188 throw new IllegalArgumentException(
189 "The loop clause must precede the where clause");
190 }
191 if (clauses.getProperty("select") == null) {
192 throw new IllegalArgumentException(
193 "The query must begin with a select clause");
194 }
195 return clauses;
196 }
197
198 private static void setClause(Properties theClauses, String theName,
199 String theClause) {
200 if (theClause != null) {
201 theClauses.setProperty(theName,
202 theClause.substring(theName.length()).trim());
203 }
204 }
205
206 private static String[] splitFromEnd(String theString, String theMarker) {
207 String[] result = new String[2];
208 int begin = theString.indexOf(theMarker);
209 if (begin >= 0) {
210 result[0] = theString.substring(0, begin);
211 result[1] = theString.substring(begin);
212 } else {
213 result[0] = theString;
214 }
215 return result;
216 }
217
218
219
220
221
222
223
224
225 public interface Result {
226
227
228
229
230
231
232
233 String get(int theFieldNumber);
234
235
236
237
238
239
240
241 String get(String theFieldName);
242
243
244
245
246 String[] getNamedFields();
247
248
249
250
251
252
253
254 boolean next() throws HL7Exception;
255
256 }
257
258 private static class ResultImpl implements Result {
259
260 private final Terser myTerser;
261 private String[] myValues;
262 private final String[] myLoopPoints;
263 private final Map<String, Integer> myLoopPointNames;
264 private final String[] myFieldPaths;
265 private final Map<String, Integer> myFieldNames;
266 private final int[] myIndices;
267 private final int[] myNumEmpty;
268
269 private final int[] myMaxNumEmpty;
270 private boolean myNonLoopingQuery = false;
271 private final String[] myWherePaths;
272 private String[] myWhereValues;
273 private final String[] myWherePatterns;
274 private final boolean[] myExactMatchFlags;
275
276 public ResultImpl(Message theMessage, String[] theLoopPoints,
277 Map<String, Integer> theLoopPointNames, String[] theFieldPaths,
278 Map<String, Integer> theFieldNames, String[] theWherePaths,
279 String[] theWherePatterns, boolean[] theExactMatchFlags) {
280
281 myTerser = new Terser(theMessage);
282 myLoopPoints = theLoopPoints;
283 myIndices = new int[theLoopPoints.length];
284 myNumEmpty = new int[theLoopPoints.length];
285 myMaxNumEmpty = getMaxNumEmpty(theLoopPoints);
286 myLoopPointNames = theLoopPointNames;
287 myFieldPaths = theFieldPaths;
288 myValues = new String[theFieldPaths.length];
289 myFieldNames = theFieldNames;
290 myWherePaths = theWherePaths;
291 myWherePatterns = theWherePatterns;
292 myExactMatchFlags = theExactMatchFlags;
293
294 if (theLoopPoints.length == 0) {
295 myNonLoopingQuery = true;
296
297 } else {
298 myIndices[myIndices.length - 1] = -1;
299
300 }
301
302 }
303
304
305
306
307 private int[] getMaxNumEmpty(String[] theLoopPoints) {
308 int[] retVal = new int[theLoopPoints.length];
309 for (int i = 0; i < theLoopPoints.length; i++) {
310 retVal[i] = getMaxNumEmpty(theLoopPoints[i]);
311 }
312 return retVal;
313 }
314
315 private int getMaxNumEmpty(String theLoopPoint) {
316 int retVal = 0;
317
318 Matcher m = Pattern.compile("\\*(\\d+)").matcher(theLoopPoint);
319 if (m.find()) {
320 String num = m.group(1);
321 retVal = Integer.parseInt(num);
322 }
323
324 return retVal;
325 }
326
327
328
329
330 private boolean currentRowValued(int theLoopPoint) {
331 for (int i = 0; i < myFieldPaths.length; i++) {
332 if (referencesLoop(myFieldPaths[i], theLoopPoint)) {
333 String value = myValues[i];
334 if (value != null && value.length() > 0) {
335 return true;
336 }
337 }
338 }
339 return false;
340 }
341
342
343 private boolean currentRowMatchesFilter() {
344 for (int i = 0; i < myWhereValues.length; i++) {
345 if (myExactMatchFlags[i]) {
346 if (!myWherePatterns[i].equals(myWhereValues[i])) {
347 return false;
348 }
349 } else {
350 if (!Pattern.matches(myWherePatterns[i], myWhereValues[i])) {
351 return false;
352 }
353 }
354 }
355 return true;
356 }
357
358
359
360 private boolean referencesLoop(String theFieldPath, int theLoopPoint) {
361 String path = theFieldPath;
362 int lp;
363 while ((lp = getLoopPointReference(path)) >= 0) {
364 if (lp == theLoopPoint) {
365 return true;
366 } else {
367 path = myLoopPoints[lp];
368 }
369 }
370 return false;
371 }
372
373
374
375
376 private String[] getCurrentValues(String[] thePaths)
377 throws HL7Exception {
378 String[] paths = composePaths(thePaths);
379 String[] values = new String[paths.length];
380 for (int i = 0; i < paths.length; i++) {
381 values[i] = myTerser.get(paths[i]);
382 if (values[i] == null) {
383 values[i] = "";
384 }
385 }
386 return values;
387 }
388
389
390
391
392 private String[] composePaths(String[] thePaths) {
393 String[] currentLoopPoints = composeLoopPoints();
394 String[] result = new String[thePaths.length];
395 for (int i = 0; i < thePaths.length; i++) {
396 result[i] = thePaths[i];
397 int ref = getLoopPointReference(thePaths[i]);
398 if (ref >= 0) {
399 result[i] = expandLoopPointReference(result[i],
400 currentLoopPoints[ref]);
401 }
402 }
403 return result;
404 }
405
406
407
408
409 private String[] composeLoopPoints() {
410 String[] result = new String[myLoopPoints.length];
411 for (int i = 0; i < myLoopPoints.length; i++) {
412 result[i] = myLoopPoints[i].replaceAll("\\*\\d*",
413 String.valueOf(myIndices[i]));
414
415 int ref = getLoopPointReference(myLoopPoints[i]);
416 if (ref >= i) {
417 throw new IllegalStateException(
418 "Loop point must be defined after the "
419 + "one it references: " + myLoopPoints[i]);
420 } else if (ref >= 0) {
421 result[i] = expandLoopPointReference(result[i], result[ref]);
422 }
423 }
424 return result;
425 }
426
427
428
429 private int getLoopPointReference(String thePath) {
430 StringTokenizer tok = new StringTokenizer(thePath, "{}", false);
431 if (thePath.indexOf('{') >= 0 && tok.hasMoreTokens()) {
432 String ref = tok.nextToken();
433 return myLoopPointNames.get(ref);
434 } else {
435 return -1;
436 }
437 }
438
439 private String expandLoopPointReference(String thePath,
440 String theLoopPoint) {
441 return thePath.replaceAll("\\{.*}", theLoopPoint);
442 }
443
444
445
446
447 public String get(int theFieldNumber) {
448 if (theFieldNumber < 0 || theFieldNumber >= myValues.length) {
449 throw new IllegalArgumentException(
450 "Field number must be between 0 and "
451 + (myValues.length - 1));
452 }
453 return myValues[theFieldNumber];
454 }
455
456
457
458
459 public String get(String theFieldName) {
460 Integer fieldNum = myFieldNames.get(theFieldName);
461 if (fieldNum == null) {
462 throw new IllegalArgumentException(
463 "Field name not recognized: " + theFieldName);
464 }
465 return get(fieldNum);
466 }
467
468
469
470
471
472 public boolean next() throws HL7Exception {
473 if (myNonLoopingQuery) {
474 myNonLoopingQuery = false;
475 myValues = getCurrentValues(myFieldPaths);
476 myWhereValues = getCurrentValues(myWherePaths);
477 return currentRowMatchesFilter();
478 }
479
480 boolean hasNext = false;
481 for (int i = myIndices.length - 1; i >= 0; i--) {
482 boolean gotMatch = false;
483 while (!gotMatch && myNumEmpty[i] <= myMaxNumEmpty[i]) {
484 myIndices[i]++;
485 myValues = getCurrentValues(myFieldPaths);
486 myWhereValues = getCurrentValues(myWherePaths);
487
488 if (!currentRowValued(i)) {
489 myNumEmpty[i]++;
490 } else {
491 myNumEmpty[i] = 0;
492 }
493 if (currentRowMatchesFilter()) {
494 gotMatch = true;
495 }
496 }
497
498 hasNext = myNumEmpty[i] <= myMaxNumEmpty[i];
499
500 if (hasNext) {
501 break;
502 }
503
504 myIndices[i] = 0;
505 myNumEmpty[i] = 0;
506
507
508
509
510
511
512
513
514
515
516
517
518 }
519 return hasNext;
520 }
521
522
523
524
525 public String[] getNamedFields() {
526 return myFieldNames.keySet().toArray(new String[0]);
527 }
528
529 }
530
531 }