001package org.hl7.fhir.r5.terminologies;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012
013import org.hl7.fhir.r5.model.Base;
014import org.hl7.fhir.r5.model.CanonicalType;
015import org.hl7.fhir.r5.model.CodeSystem;
016import org.hl7.fhir.r5.model.Coding;
017import org.hl7.fhir.r5.model.ConceptMap;
018import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
019import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
020import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
021import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
022import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ConceptMapElementSorter;
023import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ElementMappingPair;
024import org.hl7.fhir.utilities.Utilities;
025import org.hl7.fhir.r5.model.Identifier;
026import org.hl7.fhir.r5.model.Meta;
027import org.hl7.fhir.r5.model.UriType;
028import org.hl7.fhir.r5.model.ValueSet;
029
030public class ConceptMapUtilities {
031
032  public static class MappingTriple {
033    private ConceptMapGroupComponent grp;
034    private SourceElementComponent src;
035    private TargetElementComponent tgt;
036
037    public MappingTriple(ConceptMapGroupComponent grp, SourceElementComponent src, TargetElementComponent tgt) {
038      this.grp = grp;
039      this.src = src;
040      this.tgt = tgt;
041    }
042
043    public ConceptMapGroupComponent getGrp() {
044      return grp;
045    }
046
047    public SourceElementComponent getSrc() {
048      return src;
049    }
050
051    public TargetElementComponent getTgt() {
052      return tgt;
053    }
054  }
055
056  public static class TargetSorter implements Comparator<TargetElementComponent> {
057
058    @Override
059    public int compare(TargetElementComponent o1, TargetElementComponent o2) {
060      return o1.getCode().compareTo(o2.getCode());
061    }
062
063  }
064
065  public static class ElementSorter implements Comparator<SourceElementComponent> {
066
067    @Override
068    public int compare(SourceElementComponent o1, SourceElementComponent o2) {
069      return o1.getCode().compareTo(o2.getCode());
070    }
071
072  }
073
074  public static class ElementMappingPair {
075
076    private SourceElementComponent src;
077    private TargetElementComponent tgt;
078
079    public ElementMappingPair(SourceElementComponent src, TargetElementComponent tgt) {
080      this.src = src;
081      this.tgt = tgt;
082    }
083
084  }
085
086  public static class TranslatedCode {
087    private String code;
088    private ConceptMapRelationship relationship;
089    public TranslatedCode(String code, ConceptMapRelationship relationship) {
090      super();
091      this.code = code;
092      this.relationship = relationship;
093    }
094    public String getCode() {
095      return code;
096    }
097    public ConceptMapRelationship getRelationship() {
098      return relationship;
099    }
100
101  }
102
103  public static class ConceptMapElementSorter implements Comparator<SourceElementComponent> {
104
105    @Override
106    public int compare(SourceElementComponent o1, SourceElementComponent o2) {
107      return o1.getCode().compareTo(o2.getCode());
108    }
109
110  }
111
112  public static class ConceptMapTargetElementSorter implements Comparator<TargetElementComponent> {
113
114    @Override
115    public int compare(TargetElementComponent o1, TargetElementComponent o2) {
116      return o1.getCode().compareTo(o2.getCode());
117    }
118
119  }
120  public static boolean hasOID(ConceptMap cm) {
121    return getOID(cm) != null;
122  }
123
124  public static String getOID(ConceptMap cm) {
125    for (Identifier id : cm.getIdentifier()) {
126      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:"))
127        return id.getValue().substring(8);
128    }
129    return null;
130  }
131
132  public static void setOID(ConceptMap cm, String oid) {
133    if (!oid.startsWith("urn:oid:"))
134      oid = "urn:oid:" + oid;
135    for (Identifier id : cm.getIdentifier()) {
136      if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) {
137        id.setValue(oid);
138        return;
139      }
140    }
141    cm.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
142  }
143
144  public static boolean hasMappingForSource(ConceptMap cm, Coding code) {
145    return hasMappingForSource(cm, code.getSystem(), code.getVersion(), code.getCode());
146  }
147
148  public static boolean hasMappingForSource(ConceptMap cm, String system, String version, String code) {
149    for (ConceptMapGroupComponent grp : cm.getGroup()) {
150      if (system.equals(grp.getSource())) { // to do: version
151        for (SourceElementComponent e : grp.getElement()) {
152          if (code.equals(e.getCode())) {
153            return true; // doesn't matter if it's actually unmapped
154          }
155        }
156      }
157    }
158    return false;
159  }
160
161  public static boolean hasMappingForTarget(ConceptMap cm, Coding code) {
162    return hasMappingForTarget(cm, code.getSystem(), code.getVersion(), code.getCode());
163  }
164
165  public static boolean hasMappingForTarget(ConceptMap cm, String system, String version, String code) {
166    for (ConceptMapGroupComponent grp : cm.getGroup()) {
167      if (system.equals(grp.getTarget())) { // to do: version
168        for (SourceElementComponent e : grp.getElement()) {
169          for (TargetElementComponent t : e.getTarget()) {
170            if (code.equals(t.getCode())) {
171              return true; // doesn't matter if it's actually unmapped
172            }
173          }
174        }
175      }
176    }
177    return false;
178  }
179
180  public static List<Coding> listTargets(ConceptMap cm, List<String> systems) {
181    List<Coding> list = new ArrayList<>();
182    for (ConceptMapGroupComponent grp : cm.getGroup()) {
183      if (systems.isEmpty() || systems.contains(grp.getSource())) { // to do: version
184        for (SourceElementComponent e : grp.getElement()) {
185          for (TargetElementComponent t : e.getTarget()) {
186            if (t.hasCode()) {
187              list.add(new Coding(grp.getTarget(), t.getCode(), t.getDisplay()));
188            }
189          }
190        }
191      }
192    }
193    return list;
194  }
195
196
197  public static ConceptMap makeShareable(ConceptMap cm) {
198    if (!cm.hasExperimental()) {
199      cm.setExperimental(false);
200    }
201
202    if (!cm.hasMeta())
203      cm.setMeta(new Meta());
204    for (UriType t : cm.getMeta().getProfile()) 
205      if ("http://hl7.org/fhir/StructureDefinition/shareableconceptmap".equals(t.getValue()))
206        return cm;
207    cm.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareableconceptmap"));
208    return cm;
209  }
210
211  public static ConceptMap invert(ConceptMap src, String id, String url, String name, boolean collate) {
212    ConceptMap dst = src.copy();
213    dst.setId(id);
214    dst.setUrl(url);
215    dst.setName(name);
216    dst.getGroup().clear();
217    dst.setSourceScope(src.getTargetScope());
218    dst.setTargetScope(src.getSourceScope());
219    for (ConceptMapGroupComponent gs : src.getGroup()) {
220      ConceptMapGroupComponent gd = dst.addGroup();
221      gd.setTargetElement(gs.getSourceElement());
222      gd.setSourceElement(gs.getTargetElement());
223      Map<String, SourceElementComponent> dstMap = new HashMap<>();
224      for (SourceElementComponent es : gs.getElement()) {
225        for (TargetElementComponent ts : es.getTarget()) {
226          SourceElementComponent ed = collate ? dstMap.get(ts.getCode()) : null;
227          if (ed == null) {
228            ed = gd.addElement();
229            ed.setCodeElement(ts.getCodeElement());
230            if (collate) {
231              dstMap.put(ed.getCode(), ed);
232            }
233          }
234          TargetElementComponent td = ed.addTarget();
235          td.setCode(es.getCode());
236          td.setComment(ts.getComment());
237          td.setRelationship(invertRelationship(ts.getRelationship()));
238        }
239      }    
240    }
241    return dst;
242  }
243
244  private static ConceptMapRelationship invertRelationship(ConceptMapRelationship relationship) {
245    if (relationship == null) {
246      return null;
247    }
248    switch (relationship) {
249    case EQUIVALENT:
250      return ConceptMapRelationship.EQUIVALENT;
251    case NOTRELATEDTO:
252      return ConceptMapRelationship.NOTRELATEDTO;
253    case NULL:
254      return ConceptMapRelationship.NULL;
255    case RELATEDTO:
256      return ConceptMapRelationship.RELATEDTO;
257    case SOURCEISBROADERTHANTARGET:
258      return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
259    case SOURCEISNARROWERTHANTARGET:
260      return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
261    default:
262      return null;    
263    }
264  }
265
266  public static ConceptMap collapse(String id, String url, boolean cumulative, ConceptMap src, ConceptMap... sequence) {
267    ConceptMap res = src.copy();
268    res.setId(id);
269    res.setUrl(url);
270
271    for (ConceptMap cm : sequence) {
272      if (res.hasTargetScope() && cm.hasTargetScope()) {
273        if (!cm.getSourceScope().primitiveValue().equals(res.getTargetScope().primitiveValue())) {
274          throw new Error("Mismatch between sequential concept maps: target was "+res.getTargetScope()+" and source is "+cm.getSourceScope());
275        } else {
276          res.setTargetScope(cm.getTargetScope());
277        }
278      } else {
279        res.setTargetScope(null);
280      }
281    }
282
283    for (ConceptMapGroupComponent gd : res.getGroup()) {
284      for (ConceptMap cm : sequence) {
285        for (ConceptMapGroupComponent gt : cm.getGroup()) {
286          if (gt.getSource().equals(gd.getTarget())) {
287            gd.setTarget(gt.getTarget());
288
289            List<SourceElementComponent> processed = new ArrayList<ConceptMap.SourceElementComponent>();
290            for (SourceElementComponent ed : gd.getElement()) {
291              List<TargetElementComponent> list = new ArrayList<>();  
292              list.addAll(ed.getTarget());
293              ed.getTarget().clear();
294              for (TargetElementComponent ts : list) {
295                for (SourceElementComponent et : gt.getElement()) {
296                  if (et.getCode().equals(ed.getCode())) {
297                    processed.add(et);
298                    for (TargetElementComponent tt : et.getTarget()) {
299                      ed.addTarget().setCode(tt.getCode()).setRelationship(combineRelationships(ts.getRelationship(), tt.getRelationship()));
300                    }
301                  }
302                }
303              }
304              if (ed.getTarget().isEmpty()) {
305                if (cumulative) {
306                  ed.getTarget().addAll(list);
307                } else {
308                  ed.setNoMap(true);
309                }
310              }
311            }
312            if (cumulative) {
313              for (SourceElementComponent et : gt.getElement()) {
314                if (!processed.contains(et)) {
315                  gd.addElement(et.copy());
316                }
317              }
318            }
319          }
320          Collections.sort(gt.getElement(), new ConceptMapElementSorter());
321          for (SourceElementComponent e: gt.getElement()) {
322            Collections.sort(e.getTarget(), new ConceptMapTargetElementSorter());
323          }
324        }
325      }
326    }
327    return res;
328  }
329
330  public static ConceptMapRelationship combineRelationships(ConceptMapRelationship rel1, ConceptMapRelationship rel2) {
331    switch (rel1) {
332    case EQUIVALENT:
333      return rel2;
334    case NOTRELATEDTO:
335      return ConceptMapRelationship.NOTRELATEDTO;
336    case NULL:
337      return null;
338    case RELATEDTO:
339      return rel2;
340    case SOURCEISBROADERTHANTARGET:
341      switch (rel2) {
342      case EQUIVALENT:
343        return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
344      case NOTRELATEDTO:
345        return ConceptMapRelationship.NOTRELATEDTO;
346      case NULL:
347        return null;
348      case RELATEDTO:
349        return ConceptMapRelationship.RELATEDTO;
350      case SOURCEISBROADERTHANTARGET:
351        return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
352      case SOURCEISNARROWERTHANTARGET:
353        return ConceptMapRelationship.RELATEDTO;
354      }
355    case SOURCEISNARROWERTHANTARGET:
356      switch (rel2) {
357      case EQUIVALENT:
358        return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
359      case NOTRELATEDTO:
360        return ConceptMapRelationship.NOTRELATEDTO;
361      case NULL:
362        return null;
363      case RELATEDTO:
364        return ConceptMapRelationship.RELATEDTO;
365      case SOURCEISBROADERTHANTARGET:
366        return ConceptMapRelationship.RELATEDTO;
367      case SOURCEISNARROWERTHANTARGET:
368        return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
369      }
370    }
371    return null;
372  }
373
374  public static boolean checkReciprocal(ConceptMap left, ConceptMap right, List<String> issues, boolean makeChanges) {
375    boolean changed = false;
376    if (!Base.compareDeep(left.getTargetScope(), right.getSourceScope(), true)) {
377      issues.add("scopes are not reciprocal: "+left.getTargetScope()+" vs "+right.getSourceScope());
378    }
379    if (!Base.compareDeep(left.getSourceScope(), right.getTargetScope(), true)) {
380      issues.add("scopes are not reciprocal: "+left.getSourceScope()+" vs "+right.getTargetScope());
381    }
382    for (ConceptMapGroupComponent gl : left.getGroup()) {
383      ConceptMapGroupComponent gr = findMatchingGroup(right.getGroup(), gl.getTarget(), gl.getSource());
384      if (gr == null) {
385        for (SourceElementComponent e : gl.getElement()) {
386          for (TargetElementComponent t : e.getTarget()) {
387            if (t.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
388              if (makeChanges) {
389                changed = true;
390                right.forceGroup(gl.getTarget(), gl.getSource()).getOrAddElement(t.getCode()).addTarget(e.getCode(), inverse(t.getRelationship()));
391              } else {
392                issues.add("left maps from "+gl.getSource()+"#"+e.getCode()+" to "+gl.getTarget()+"#"+t.getCode()+" but right has no matching reverse map");
393              }
394            } 
395          }
396        }
397      } else {
398        for (SourceElementComponent srcL : gl.getElement()) {
399          if (!srcL.getNoMap()) {
400            for (TargetElementComponent tgtL : srcL.getTarget()) {
401              List<ElementMappingPair> pairs = getMappings(gr, tgtL.getCode(), srcL.getCode());
402              if (tgtL.getRelationship() == null) {
403                issues.add("Left map has relationship "+srcL.getCode()+" with no relationship");
404              } else switch (tgtL.getRelationship()) {
405              case EQUIVALENT:
406                if (pairs.isEmpty()) {
407                  if (makeChanges) {
408                    changed = true;
409                    gr.getOrAddElement(tgtL.getCode()).addTarget(srcL.getCode(), ConceptMapRelationship.EQUIVALENT);
410                  } else {
411                    issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but there's no reverse relationship");
412                  }
413                } else for (ElementMappingPair pair : pairs) {
414                  if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
415                    issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
416                  }
417                }
418                break;
419              case RELATEDTO:
420                if (pairs.isEmpty()) {
421                  issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but there's no reverse relationship");
422                } else for (ElementMappingPair pair : pairs) {
423                  if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
424                    issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
425                  }
426                }
427                break;
428              case SOURCEISBROADERTHANTARGET:
429                if (pairs.isEmpty()) {
430                  issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but there's no reverse relationship");
431                } else for (ElementMappingPair pair : pairs) {
432                  if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
433                    issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
434                  }
435                }
436                break;
437              case SOURCEISNARROWERTHANTARGET:
438                if (pairs.isEmpty()) {
439                  issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but there's no reverse relationship");
440                } else for (ElementMappingPair pair : pairs) {
441                  if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
442                    issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
443                  }
444                }
445                break;
446              case NOTRELATEDTO:
447                for (ElementMappingPair pair : pairs) {
448                  if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
449                    issues.add("Left map says that "+srcL.getCode()+" is not related to "+tgtL.getCode()+" but a reverse relationship exists with type "+pair.tgt.getRelationship().toCode());
450                  }
451                }
452                break;
453              }
454            }
455          } else {
456            for (SourceElementComponent srcR : gr.getElement()) {
457              for (TargetElementComponent tgtR : srcR.getTarget()) {
458                if (srcL.getCode().equals(tgtR.getCode())) {
459                  issues.add("Left map says that there is no relationship for "+srcL.getCode()+" but right map has a "+tgtR.getRelationship().toCode()+" mapping to it from "+srcR.getCode());
460                }
461              }
462            }
463          }
464        }
465      }
466    }
467    for (ConceptMapGroupComponent gr : right.getGroup()) {
468      ConceptMapGroupComponent gl = findMatchingGroup(left.getGroup(), gr.getTarget(), gr.getSource());
469      if (gl == null) {
470        for (SourceElementComponent e : gr.getElement()) {
471          for (TargetElementComponent t : e.getTarget()) {
472            if (t.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
473              if (makeChanges) {
474                changed = true;
475                left.forceGroup(gr.getTarget(), gr.getSource()).getOrAddElement(t.getCode()).addTarget(e.getCode(), inverse(t.getRelationship()));
476              } else {
477                issues.add("left maps from "+gr.getSource()+"#"+e.getCode()+" to "+gr.getTarget()+"#"+t.getCode()+" but right has no matching reverse map");
478              }
479            } 
480          }
481        }
482      } else {
483        for (SourceElementComponent srcR : gr.getElement()) {
484          if (!"CHECK!".equals(srcR.getCode())) {
485            if (!srcR.getNoMap()) {
486              for (TargetElementComponent tgtR : srcR.getTarget()) {
487                List<ElementMappingPair> pairs = getMappings(gl, tgtR.getCode(), srcR.getCode());
488                if (tgtR.getRelationship() == null) {
489                  issues.add("Right map has relationship "+srcR.getCode()+" with no relationship");
490                } else switch (tgtR.getRelationship()) {
491                case EQUIVALENT:
492                  if (pairs.isEmpty()) {
493                    if (makeChanges) {
494                      changed = true;
495                      gl.getOrAddElement(tgtR.getCode()).addTarget(srcR.getCode(), ConceptMapRelationship.EQUIVALENT);
496                    } else {
497                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but there's no reverse relationship");
498                    }
499                  } else for (ElementMappingPair pair : pairs) {
500                    if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
501                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
502                    }
503                  }
504                  break;
505                case RELATEDTO:
506                  if (pairs.isEmpty()) {
507                    issues.add("Right map says that "+srcR.getCode()+" is related to "+tgtR.getCode()+" but there's no reverse relationship");
508                  } else for (ElementMappingPair pair : pairs) {
509                    if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
510                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
511                    }
512                  }
513                  break;
514                case SOURCEISBROADERTHANTARGET:
515                  if (pairs.isEmpty()) {
516                    issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but there's no reverse relationship");
517                  } else for (ElementMappingPair pair : pairs) {
518                    if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
519                      issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
520                    }
521                  }
522                  break;
523                case SOURCEISNARROWERTHANTARGET:
524                  if (pairs.isEmpty()) {
525                    issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but there's no reverse relationship");
526                  } else for (ElementMappingPair pair : pairs) {
527                    if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
528                      issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
529                    }
530                  }
531                  break;
532                case NOTRELATEDTO:
533                  for (ElementMappingPair pair : pairs) {
534                    if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
535                      issues.add("Right map says that "+srcR.getCode()+" is not related to "+tgtR.getCode()+" but a reverse relationship exists with type "+pair.tgt.getRelationship().toCode());
536                    }
537                  }
538                  break;
539                }
540              }
541            } else {
542              for (SourceElementComponent srcL : gr.getElement()) {
543                for (TargetElementComponent tgtL : srcL.getTarget()) {
544                  if (srcR.getCode().equals(tgtL.getCode())) {
545                    issues.add("Right map says that there is no relationship for "+srcR.getCode()+" but right map has a "+tgtL.getRelationship().toCode()+" mapping to it from "+srcL.getCode());
546                  }
547                }
548              }
549            }
550          }
551        }
552      }
553    }
554    return changed;
555  }
556
557  private static ConceptMapRelationship inverse(ConceptMapRelationship relationship) {
558    switch (relationship) {
559    case EQUIVALENT: return ConceptMapRelationship.EQUIVALENT;
560    case RELATEDTO: return ConceptMapRelationship.RELATEDTO;
561    case SOURCEISBROADERTHANTARGET: return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
562    case SOURCEISNARROWERTHANTARGET: return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
563    default: return null;
564    }
565  }
566
567  private static boolean hasActualMappings(ConceptMapGroupComponent gr) {
568    for (SourceElementComponent e : gr.getElement()) {
569      for (TargetElementComponent tgt : e.getTarget()) {
570        if (tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
571          return true;
572        }
573      }
574    }
575    return false;
576  }
577
578  private static List<ElementMappingPair> getMappings(ConceptMapGroupComponent g, String source, String target) {
579    List<ElementMappingPair> res = new ArrayList<ConceptMapUtilities.ElementMappingPair>();
580
581    for (SourceElementComponent src : g.getElement()) {
582      for (TargetElementComponent tgt : src.getTarget()) {
583        if (source.equals(src.getCode()) && target.equals(tgt.getCode())) {
584          res.add(new ElementMappingPair(src, tgt));
585        }
586      }
587    }
588    return res;
589  }
590
591  private static ConceptMapGroupComponent findMatchingGroup(List<ConceptMapGroupComponent> groups, String source, String target) {
592    for (ConceptMapGroupComponent g : groups) {
593      if (source.equals(g.getSource()) && target.equals(g.getTarget())) {
594        return g;
595      }
596    }
597    return null;
598  }
599
600  /** 
601   * 
602   * @param cmF
603   * @return true if all the maps simply map to the same code
604   */
605  public static boolean isUnityMap(ConceptMap cm) {
606    for (ConceptMapGroupComponent grp : cm.getGroup()) {
607      for (SourceElementComponent src : grp.getElement()) {
608        if (src.hasNoMap()) {
609          return false;
610        }
611        if (src.getTarget().size() != 1) {
612          return false;
613        }
614        if (src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.EQUIVALENT && src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.RELATEDTO) {
615          return false;
616        }
617        if (!src.getCode().equals(src.getTargetFirstRep().getCode())) {
618          return false;
619        }
620      }
621    }
622    return true;
623  }
624
625  public static int mapCount(ConceptMap cm) {
626    int i = 0;
627    for (ConceptMapGroupComponent grp : cm.getGroup()) {
628      for (SourceElementComponent src : grp.getElement()) {
629        i = i + src.getTarget().size();
630      }
631    }
632    return i;
633  }
634
635  public static Set<Coding> listCodesWithNoMappings(Set<Coding> codes, ConceptMap map) {
636    Set<Coding> res = new HashSet<>();
637    for (Coding c : codes) {
638      if (c != null && c.hasCode()) {
639        boolean found = false;
640        for (ConceptMapGroupComponent grp : map.getGroup()) {
641          if (matchesCoding(grp, c)) {
642            for (SourceElementComponent src : grp.getElement()) {
643              if (c.getCode().equals(src.getCode())) {
644                for (TargetElementComponent tgt : src.getTarget()) {
645                  if (tgt.getRelationship() == ConceptMapRelationship.RELATEDTO || tgt.getRelationship() == ConceptMapRelationship.EQUIVALENT || tgt.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
646                    found = true;                
647                  }
648                }
649              }
650            }
651          }
652        }
653        if (!found) {
654          res.add(c);
655        }
656      }
657    }    
658    return res;
659  }
660
661  private static boolean matchesCoding(ConceptMapGroupComponent grp, Coding code) {    
662    return code.getSystem().equals(grp.getSource()) || (code.getSystem()+"|"+code.getVersion()).equals(grp.getSource());
663  }
664
665  public static List<String> translateCode(String name, String defaultValue, ConceptMap... cmList) {
666    List<String> res = translateCode(name, cmList);
667    if (res.isEmpty()) {
668      res.add(defaultValue);
669    }
670    return res;
671  }
672  public static List<String> translateCode(String name, ConceptMap... cmList) {
673    List<String> res = new ArrayList<>();
674    res.add(name);
675    for (ConceptMap cm : cmList) {
676      res = translateCodes(res, cm);
677    }
678    return res;
679  }
680
681  private static List<String> translateCodes(List<String> codes, ConceptMap cm) {
682    List<String> res = new ArrayList<>();
683    for (ConceptMapGroupComponent g : cm.getGroup()) {
684      for (SourceElementComponent e : g.getElement()) {
685        if (Utilities.existsInList(e.getCode(), codes)) {
686          for (TargetElementComponent t : e.getTarget()) {
687            if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO || 
688                t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
689              res.add(t.getCode());
690            }
691          }
692        }
693      }
694    }
695    return res;
696  }
697
698  public static List<Coding> translateCoding(Coding code, ConceptMap... cmList) {
699    List<Coding> res = new ArrayList<>();
700    for (ConceptMap cm : cmList) {
701      res = translateCodings(res, cm);
702    }
703    return res;
704  }
705
706  private static List<Coding> translateCodings(List<Coding> codes, ConceptMap cm) {
707    List<Coding> res = new ArrayList<>();
708    for (ConceptMapGroupComponent g : cm.getGroup()) {
709      for (SourceElementComponent e : g.getElement()) {
710        if (hasCode(g.getSource(), e.getCode(), codes)) {
711          for (TargetElementComponent t : e.getTarget()) {
712            if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO || 
713                t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
714              res.add(new Coding().setSystem(g.getTarget()).setCode((t.getCode())));
715            }
716          }
717        }
718      }
719    }
720    return res;
721  }
722
723  private static boolean hasCode(String system, String code, List<Coding> codes) {
724    for (Coding c : codes) {
725      if (system.equals(c.getSystem()) && code.equals(c.getCode())) {
726        return true;
727      }
728    }
729    return false;
730  }
731
732  public static List<MappingTriple> getBySource(ConceptMap map, Coding c) {
733    List<MappingTriple> list = new ArrayList<>();
734    for (ConceptMapGroupComponent g : map.getGroup()) {
735      if (CanonicalType.matches(g.getSource(), c.getSystem(), c.getVersion())) {
736        for (SourceElementComponent e : g.getElement()) {
737          if (e.getCode().equals(c.getCode())) {
738            if (e.getNoMap()) {
739              list.add(new MappingTriple(g, e, null));
740            } else {
741              for (TargetElementComponent t : e.getTarget()) {
742                list.add(new MappingTriple(g, e, t));
743              }
744            }
745          }
746        }
747      }
748    }
749    return list;
750  }
751
752  public static List<MappingTriple> getByTarget(ConceptMap map, Coding c) {
753    List<MappingTriple> list = new ArrayList<>();
754    for (ConceptMapGroupComponent g : map.getGroup()) {
755      if (CanonicalType.matches(g.getTarget(), c.getSystem(), c.getVersion())) {
756        for (SourceElementComponent e : g.getElement()) {
757          for (TargetElementComponent t : e.getTarget()) {
758            if (t.getCode().equals(c.getCode())) {
759              list.add(new MappingTriple(g, e, t));
760            }
761          }
762        }
763      }
764    }
765    return list;
766  }
767
768}