1 package com.exsoinn.util.epf;
2
3 import net.jcip.annotations.Immutable;
4
5 import java.util.*;
6 import java.util.concurrent.ConcurrentHashMap;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9 import java.util.stream.Collectors;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 @Immutable
38 abstract class AbstractContext implements Context {
39 private static final char WILD_CARD = '*';
40 private static final Map<String, Pattern> patternCache = new ConcurrentHashMap<>();
41 private static final String ANON_ARY_HANDLE = "anonymousArray";
42
43
44 @Override
45 public SearchResult findElement(SearchPath pSearchPath,
46 Filter pFilter,
47 TargetElements pTargetElements,
48 Map<String, String> pExtraParams)
49 throws IllegalArgumentException {
50 Map<String, Context> found = findElement(this, pSearchPath, pFilter, pTargetElements, null, pExtraParams);
51 return SearchResult.createSearchResult(found);
52 }
53
54 @Override
55 public SearchResult findElement(SelectionCriteria pSelectCriteria,
56 Map<String, String> pExtraParams) throws IllegalArgumentException {
57 return findElement(
58 pSelectCriteria.getSearchPath(), pSelectCriteria.getFilter(), pSelectCriteria.getTargetElements(), pExtraParams);
59 }
60
61
62 Map<String, Context> findElement(Context pElem,
63 SearchPath pSearchPath,
64 Filter pFilter,
65 TargetElements pTargetElements,
66 Map<String, Context> pFoundElemVals,
67 Map<String, String> pExtraParams)
68 throws IllegalArgumentException {
69
70 if (null == pFoundElemVals) {
71 pFoundElemVals = new HashMap<>(pTargetElements != null ? pTargetElements.size() : 0);
72 }
73
74 String curNodeInPath;
75
76
77
78
79
80 pSearchPath = pSearchPath.advanceToNextNode();
81 curNodeInPath = pSearchPath.currentNode();
82
83
84
85
86
87
88
89
90 if (pSearchPath.currentNodeIndex() == 0 && curNodeInPath.indexOf("[") == 0 && pElem.isArray()) {
91 MutableContext mc = ContextFactory.obtainMutableContext("{}");
92 mc.addMember(ANON_ARY_HANDLE, ContextFactory.obtainContext(pElem.stringRepresentation()));
93 curNodeInPath = ANON_ARY_HANDLE + curNodeInPath;
94 pElem = mc;
95 }
96
97 String curNodeInPathNoBrackets = curNodeInPath;
98 if (arrayIndex(curNodeInPath) >= 0) {
99 curNodeInPathNoBrackets = removeBrackets(curNodeInPath);
100 }
101
102 boolean atEndOfSearchPath = pSearchPath.isAtEndOfSearchPath();
103
104
105
106
107
108
109
110
111
112
113 Set<Map.Entry<String, Context>> elemEntries = null;
114 if (pElem.isRecursible()) {
115
116
117
118
119
120
121
122 if (pElem.containsElement(curNodeInPathNoBrackets) && (arrayIndex(curNodeInPath) <= 0
123 || pElem.memberValue(curNodeInPathNoBrackets).isArray())) {
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 elemEntries = pElem.entrySet();
146 } else {
147
148
149
150
151
152
153
154 if (null != pExtraParams && pExtraParams.containsKey(IGNORE_INCOMPATIBLE_SEARCH_PATH_PROVIDED_ERROR)) {
155
156
157
158
159
160 return pFoundElemVals;
161 } else {
162 IncompatibleSearchPathException ispe = new IncompatibleSearchPathException(
163 pSearchPath, curNodeInPathNoBrackets, pElem);
164 throw new IllegalArgumentException(ispe);
165 }
166
167 }
168 }
169
170
171
172
173
174
175
176 if (null != elemEntries) {
177 for (Map.Entry<String, Context> elemEntry : elemEntries) {
178
179
180
181
182
183
184 if (!pFoundElemVals.isEmpty()) {
185 return pFoundElemVals;
186 }
187
188 String curElemName = elemEntry.getKey();
189
190 if (!curNodeInPathNoBrackets.equals(curElemName)) {
191 continue;
192 }
193
194 Context elemToProcessNext = elemEntry.getValue();
195
196
197
198
199
200
201
202 if (elemToProcessNext.isArray()) {
203
204
205
206
207
208
209
210
211
212
213
214 if (arrayIndex(curNodeInPath) < 0 && !atEndOfSearchPath && elemToProcessNext.asArray().size() > 1) {
215 UnexpectedArrayNodeException uane =
216 new UnexpectedArrayNodeException(pSearchPath, curNodeInPath, elemToProcessNext);
217 throw new IllegalArgumentException(uane);
218 }
219
220
221
222
223
224
225
226
227 int aryIdx;
228 if ((aryIdx = arrayIndex(curNodeInPath)) >= 0) {
229
230
231
232
233 if (aryIdx >= elemToProcessNext.asArray().size()) {
234 if (null != pExtraParams && pExtraParams.containsKey(IGNORE_INCOMPATIBLE_SEARCH_PATH_PROVIDED_ERROR)) {
235 return pFoundElemVals;
236 } else {
237 IncompatibleSearchPathException ispe = new IncompatibleSearchPathException(
238 pSearchPath, curNodeInPath, elemToProcessNext);
239 throw new IllegalArgumentException(ispe);
240 }
241 }
242
243 elemToProcessNext = elemToProcessNext.entryFromArray(aryIdx);
244 }
245 }
246
247
248
249
250
251
252
253
254
255 if (atEndOfSearchPath) {
256 processElement(curElemName, elemToProcessNext, pFilter, pTargetElements, pFoundElemVals, pExtraParams);
257 } else if (elemToProcessNext.isRecursible()) {
258 findElement(elemToProcessNext, pSearchPath, pFilter, pTargetElements, pFoundElemVals, pExtraParams);
259 }
260 }
261 }
262
263 return pFoundElemVals;
264 }
265
266
267
268
269
270
271
272
273
274 private int arrayIndex(String pNode) {
275 if (pNode.indexOf('[') < 0) {
276 return -1;
277 }
278 return Integer.parseInt(pNode.substring(pNode.indexOf('[') + 1, pNode.indexOf(']')));
279 }
280
281
282 private void processElement(String pElemName,
283 Context pElem,
284 Filter pFilter,
285 TargetElements pTargetElements,
286 Map<String, Context> pFoundElemVals,
287 Map<String, String> pExtraParams) throws IllegalArgumentException {
288 Context elemValToStore = null;
289
290
291
292 if (pElem.isPrimitive() || pElem.isRecursible()) {
293
294
295
296
297
298
299 if (shouldExcludeFromResults(pElemName, pElem, pFilter, pExtraParams)) {
300 return;
301 }
302
303 elemValToStore = pElem;
304
305
306
307
308 if (pElem.isRecursible()) {
309 elemValToStore = filterUnwantedElements(pElem, pTargetElements, pExtraParams);
310 }
311 } else if (pElem.isArray()) {
312 Iterator<Context> itElem = pElem.asArray().iterator();
313 List<Object> elemValList = new ArrayList<>();
314 itElem.forEachRemaining(elem -> {
315
316
317
318
319
320
321
322
323 if (!shouldExcludeFromResults(pElemName, elem, pFilter, pExtraParams)) {
324 if (elem.isRecursible()) {
325
326
327
328
329 elem = filterUnwantedElements(elem, pTargetElements, pExtraParams);
330 }
331 elemValList.add(elem.toString());
332 }
333 });
334
335
336
337
338
339
340
341
342 if (!elemValList.isEmpty()) {
343 elemValToStore = ContextFactory.INSTANCE.obtainContext(elemValList);
344 }
345 } else {
346 throw new IllegalArgumentException("One of the elements to search is of type not currently supported."
347 + "Element name/type is " + pElemName + "/" + pElem.getClass().getName());
348 }
349
350 if (null != elemValToStore) {
351
352
353
354
355 pFoundElemVals.put(pElemName, elemValToStore);
356 handleSingleComplexObjectFound(pFoundElemVals, pTargetElements);
357 }
358 }
359
360
361
362
363
364
365
366
367
368 Context filterUnwantedElements(Context pElem, TargetElements pTargetElems, Map<String, String> pExtraParams) {
369 if (null == pTargetElems) {
370 return pElem;
371 }
372
373 MutableContext mc = ContextFactory.INSTANCE.obtainMutableContext("{}");
374
375
376
377
378 for (String e : pTargetElems) {
379 if (!stringIsASearchPath(e)) {
380 continue;
381 }
382 SearchPath sp = SearchPath.valueOf(e);
383 SearchResult sr = pElem.findElement(sp, null, null, pExtraParams);
384
385 if (null == sr || sr.size() != 1) {
386
387
388
389
390 if (null != pExtraParams && pExtraParams.containsKey(IGNORE_INCOMPATIBLE_TARGET_ELEMENT_PROVIDED_ERROR) &&
391 (sr == null || sr.isEmpty())) {
392 continue;
393 } else {
394 throw new IllegalArgumentException("Either found more than one element for target element search path "
395 + sp.toString() + ", or did not find any results. Check the search path and try again. Results "
396 + "were " + (null == sr ? "NULL" : "EMPTY") + ". Target elements is " + pTargetElems.toString()
397 + " and node is " + pElem.stringRepresentation());
398 }
399 }
400
401 Map.Entry<String, Context> found = sr.entrySet().iterator().next();
402 mc.addMember(sp.toString(), found.getValue());
403 }
404
405
406
407
408 Set<Map.Entry<String, Context>> ents = pElem.entrySet();
409 ents.stream().filter(entry -> pTargetElems.contains(entry.getKey()))
410 .forEach(entry -> mc.addMember(entry.getKey(), entry.getValue()));
411
412 return ContextFactory.INSTANCE.obtainContext(mc.stringRepresentation());
413 }
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428 void handleSingleComplexObjectFound(Map<String, Context> pSearchRes,
429 Set<String> pTargetElems) {
430 if (null == pTargetElems || pTargetElems.isEmpty()) {
431 return;
432 }
433 try {
434 Set<Map.Entry<String, Context>> entries = pSearchRes.entrySet();
435 if (entries.size() != 1) {
436 return;
437 }
438
439 final Context elem = entries.iterator().next().getValue();
440 Context ctx;
441
442 if (elem.isArray() && elem.asArray().size() == 1) {
443 Context aryElem = elem.asArray().iterator().next();
444 ctx = aryElem;
445 } else {
446 ctx = elem;
447 }
448
449
450
451
452
453
454
455 if (!ctx.isRecursible() || ctx.entrySet().size() > 1) {
456 return;
457 }
458
459 if (null != ctx) {
460 pSearchRes.clear();
461 Set<Map.Entry<String, Context>> ctxEntSet = ctx.entrySet();
462
463 for (Map.Entry<String, Context> entry : ctxEntSet) {
464 if (null != pTargetElems && pTargetElems.contains(entry.getKey())) {
465
466
467
468
469 String k = entry.getKey();
470 if (stringIsASearchPath(k)) {
471 k = SearchPath.valueOf(k).lastNode();
472 }
473 pSearchRes.put(k, entry.getValue());
474 }
475 }
476 }
477 } catch (Exception e) {
478 System.err.println("There was a problem: " + e);
479 }
480 }
481
482 private boolean stringIsASearchPath(String pStr) {
483 return pStr.indexOf(".") > 0;
484 }
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 boolean shouldExcludeFromResults(String pElemName, Context pElem, Filter pFilter, Map<String, String> pExtraParams)
511 throws IllegalArgumentException {
512 if (null == pFilter) {
513 return false;
514 }
515
516 if (pElem.isArray()) {
517 throw new IllegalArgumentException("Got an array element when applying search filter "
518 + pFilter.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining())
519 + ". The element in question is " + pElemName + " ===>>> " + pElem.stringRepresentation());
520 }
521
522
523
524
525
526 StringBuilder filterNotApplicableReason = new StringBuilder();
527 if (!filterIsApplicableToFoundElement(pElem, pElemName, pFilter, filterNotApplicableReason)) {
528 throw new IllegalArgumentException("Filter not applicable to found element: " + filterNotApplicableReason.toString());
529 }
530
531 Set<Map.Entry<String, String>> filterEntries = pFilter.entrySet();
532 for (Map.Entry<String, String> filterEntry : filterEntries) {
533 String filterKey = filterEntry.getKey();
534 String filterVal = filterEntry.getValue();
535
536 if (stringIsASearchPath(filterKey)) {
537
538
539
540
541
542
543
544
545
546 SearchPath elemSearchPath = SearchPath.valueOf(filterKey);
547
548
549 if (!pElem.containsElement(removeBrackets(elemSearchPath.get(0)))) {
550
551
552
553
554 return true;
555 }
556
557
558 Map<String, Context> nestedElemSearchRes;
559 nestedElemSearchRes = findElement(pElem, elemSearchPath, null, null, null, pExtraParams);
560 Context nestedElemCtx;
561 if (null != nestedElemSearchRes && nestedElemSearchRes.size() > 0) {
562 Set<Map.Entry<String, Context>> entries = nestedElemSearchRes.entrySet();
563 nestedElemCtx = entries.iterator().next().getValue();
564 } else {
565
566
567
568
569 if (null == pExtraParams || !pExtraParams.containsKey(IGNORE_INCOMPATIBLE_SEARCH_PATH_PROVIDED_ERROR)) {
570 throw new IllegalArgumentException("The filter element value specified was not found off of this node: " +
571 filterKey);
572 } else {
573 return true;
574 }
575 }
576
577 if (!filterValueMatches(nestedElemCtx, filterVal, pExtraParams)) {
578 return true;
579 }
580 } else {
581 Context elem;
582 if (pElem.isPrimitive()) {
583 elem = pElem;
584 } else {
585 elem = pElem.memberValue(filterKey);
586 }
587
588
589 if (!filterValueMatches(elem, filterVal, pExtraParams)) {
590 return true;
591 }
592 }
593 }
594 return false;
595 }
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 boolean filterValueMatches(Context pFoundElem, String pFilterVal, Map<String, String> pExtraParams)
611 throws IllegalArgumentException {
612
613 if (pFoundElem.isArray()) {
614
615
616
617
618
619
620 return pFoundElem.asArray()
621 .parallelStream().anyMatch(v -> filterValueAndFoundValueMatch(v.stringRepresentation(),
622 pFilterVal, pExtraParams));
623
624 } else {
625 return filterValueAndFoundValueMatch(pFoundElem.stringRepresentation(), pFilterVal, pExtraParams);
626
627 }
628 }
629
630
631
632
633
634
635
636
637
638
639
640
641 boolean filterValueAndFoundValueMatch(String pFoundVal, String pFilterVal, Map<String, String> pExtraParams)
642 throws IllegalArgumentException {
643
644
645
646
647
648 if (null != pExtraParams && pExtraParams.containsKey(FOUND_ELEM_VAL_IS_REGEX)) {
649
650
651
652
653 Pattern p = patternCache.get(pFoundVal);
654 if (null == p) {
655 p = Pattern.compile(pFoundVal);
656 Pattern prevPatt = patternCache.putIfAbsent(pFoundVal, p);
657 if (null != prevPatt) {
658 p = prevPatt;
659 }
660 }
661 List<String> filterVals = buildListFilter(pFilterVal);
662
663 for (String f : filterVals) {
664 Matcher m = p.matcher(f);
665 if (pExtraParams.containsKey(PARTIAL_REGEX_MATCH)) {
666 if (m.find()) {
667 return true;
668 }
669 } else {
670 if (m.matches()) {
671 return true;
672 }
673 }
674 }
675
676 return false;
677 }
678
679
680
681
682
683
684
685 List<String> filterVals = buildListFilter(pFilterVal);
686 for (String filterVal : filterVals) {
687 boolean unsupportedWildCardPlacement = true;
688
689 if (filterVal.indexOf(WILD_CARD) < 0) {
690 unsupportedWildCardPlacement = false;
691 if (pFoundVal.equals(filterVal)) {
692 return true;
693 }
694 }
695
696 String filterValWithoutWildCard = filterVal.replaceAll("\\*", "");
697
698 if (filterVal.charAt(0) == WILD_CARD && filterVal.charAt(filterVal.length() - 1) == WILD_CARD) {
699 unsupportedWildCardPlacement = false;
700 if (pFoundVal.indexOf(filterValWithoutWildCard) >= 0) {
701 return true;
702 }
703 } else if (filterVal.charAt(0) == WILD_CARD) {
704 unsupportedWildCardPlacement = false;
705
706 if (pFoundVal.lastIndexOf(filterValWithoutWildCard) == pFoundVal.length() - filterValWithoutWildCard.length()) {
707 return true;
708 }
709
710 } else if (filterVal.charAt(filterVal.length() - 1) == WILD_CARD) {
711 unsupportedWildCardPlacement = false;
712
713 if (pFoundVal.indexOf(filterValWithoutWildCard) == 0) {
714 return true;
715 }
716 }
717
718 if (unsupportedWildCardPlacement) {
719 throw new IllegalArgumentException("Illegal placement of wildcard character '" + WILD_CARD
720 + "' found in filter value '" + filterVal + "'. Only begin/end, or either begin or end wildcard"
721 + " placement is supported.");
722 }
723 }
724
725 return false;
726 }
727
728
729 private List<String> buildListFilter(String pFilterVal) {
730 List<String> filterVals;
731 if (null == (filterVals = Context.transformArgumentToListObject(pFilterVal))) {
732 filterVals = new ArrayList<>();
733 filterVals.add(pFilterVal);
734 }
735
736 return filterVals;
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757 boolean filterIsApplicableToFoundElement(Context pFoundCtx, String pFoundElemName, Filter pFilter, StringBuilder pReason) {
758 if (pFoundCtx.isPrimitive() && pFilter.size() > 1) {
759 if (null != pReason) {
760 pReason.append("Found element " + pFoundElemName + " ===>>> " + pFoundCtx.stringRepresentation()
761 + " is a primitive yet filter contained more than one entry: "
762 + pFilter.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining()));
763 }
764 return false;
765 }
766 for (Map.Entry<String, String> e : pFilter.entrySet()) {
767 String k = e.getKey();
768 boolean applies = true;
769
770 if (pFoundCtx.isPrimitive()) {
771 if (!pFoundElemName.equals(k)) {
772 if (null != pReason) {
773 pReason.append("The supplied filter key does not match for found primitive element name: " + pFoundElemName
774 + " ===>>> " + pFoundCtx.stringRepresentation() + ". Filter is "
775 + pFilter.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining()));
776 }
777 applies = false;
778 }
779 } else {
780 if (stringIsASearchPath(k)) {
781 continue;
782 }
783 if (!pFoundCtx.containsElement(k)) {
784 if (null != pReason) {
785 pReason.append("The supplied filter key is not found in complex object: " + pFoundElemName
786 + " ===>>> " + pFoundCtx.stringRepresentation() + ". Filter is key " + k
787 + ". Full filter provided is "
788 + pFilter.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining(";")));
789 }
790 applies = false;
791 }
792 }
793
794 if (!applies) {
795 return false;
796 }
797 }
798
799 return true;
800 }
801
802
803 @Override
804 public SearchPath startSearchPath() {
805 if (isRecursible() && entrySet().size() == 1) {
806 return SearchPath.valueOf(entrySet().iterator().next().getKey());
807 }
808
809 return null;
810 }
811
812 public List<String> topLevelElementNames() {
813 if (isRecursible()) {
814 return entrySet().parallelStream().map(Map.Entry::getKey).collect(Collectors.toList());
815 }
816
817 return null;
818 }
819
820
821 private String removeBrackets(String pIn) {
822 if (arrayIndex(pIn) < 0) {
823 return pIn;
824 }
825 return pIn.substring(0, pIn.indexOf('['));
826 }
827 }