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, boolean extension) {
198    if (!cm.hasExperimental()) {
199      cm.setExperimental(false);
200    }
201
202    if (extension) {
203      if (!cm.hasMeta())
204        cm.setMeta(new Meta());
205      for (UriType t : cm.getMeta().getProfile())
206        if ("http://hl7.org/fhir/StructureDefinition/shareableconceptmap".equals(t.getValue()))
207          return cm;
208      cm.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareableconceptmap"));
209    }
210    return cm;
211  }
212
213  public static ConceptMap invert(ConceptMap src, String id, String url, String name, boolean collate) {
214    ConceptMap dst = src.copy();
215    dst.setId(id);
216    dst.setUrl(url);
217    dst.setName(name);
218    dst.getGroup().clear();
219    dst.setSourceScope(src.getTargetScope());
220    dst.setTargetScope(src.getSourceScope());
221    for (ConceptMapGroupComponent gs : src.getGroup()) {
222      ConceptMapGroupComponent gd = dst.addGroup();
223      gd.setTargetElement(gs.getSourceElement());
224      gd.setSourceElement(gs.getTargetElement());
225      Map<String, SourceElementComponent> dstMap = new HashMap<>();
226      for (SourceElementComponent es : gs.getElement()) {
227        for (TargetElementComponent ts : es.getTarget()) {
228          SourceElementComponent ed = collate ? dstMap.get(ts.getCode()) : null;
229          if (ed == null) {
230            ed = gd.addElement();
231            ed.setCodeElement(ts.getCodeElement());
232            if (collate) {
233              dstMap.put(ed.getCode(), ed);
234            }
235          }
236          TargetElementComponent td = ed.addTarget();
237          td.setCode(es.getCode());
238          td.setComment(ts.getComment());
239          td.setRelationship(invertRelationship(ts.getRelationship()));
240        }
241      }    
242    }
243    return dst;
244  }
245
246  private static ConceptMapRelationship invertRelationship(ConceptMapRelationship relationship) {
247    if (relationship == null) {
248      return null;
249    }
250    switch (relationship) {
251    case EQUIVALENT:
252      return ConceptMapRelationship.EQUIVALENT;
253    case NOTRELATEDTO:
254      return ConceptMapRelationship.NOTRELATEDTO;
255    case NULL:
256      return ConceptMapRelationship.NULL;
257    case RELATEDTO:
258      return ConceptMapRelationship.RELATEDTO;
259    case SOURCEISBROADERTHANTARGET:
260      return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
261    case SOURCEISNARROWERTHANTARGET:
262      return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
263    default:
264      return null;    
265    }
266  }
267
268  public static ConceptMap collapse(String id, String url, boolean cumulative, ConceptMap src, ConceptMap... sequence) {
269    ConceptMap res = src.copy();
270    res.setId(id);
271    res.setUrl(url);
272
273    for (ConceptMap cm : sequence) {
274      if (res.hasTargetScope() && cm.hasTargetScope()) {
275        if (!cm.getSourceScope().primitiveValue().equals(res.getTargetScope().primitiveValue())) {
276          throw new Error("Mismatch between sequential concept maps: target was "+res.getTargetScope()+" and source is "+cm.getSourceScope());
277        } else {
278          res.setTargetScope(cm.getTargetScope());
279        }
280      } else {
281        res.setTargetScope(null);
282      }
283    }
284
285    for (ConceptMapGroupComponent gd : res.getGroup()) {
286      for (ConceptMap cm : sequence) {
287        for (ConceptMapGroupComponent gt : cm.getGroup()) {
288          if (gt.getSource().equals(gd.getTarget())) {
289            gd.setTarget(gt.getTarget());
290
291            List<SourceElementComponent> processed = new ArrayList<ConceptMap.SourceElementComponent>();
292            for (SourceElementComponent ed : gd.getElement()) {
293              List<TargetElementComponent> list = new ArrayList<>();  
294              list.addAll(ed.getTarget());
295              ed.getTarget().clear();
296              for (TargetElementComponent ts : list) {
297                for (SourceElementComponent et : gt.getElement()) {
298                  if (et.getCode().equals(ed.getCode())) {
299                    processed.add(et);
300                    for (TargetElementComponent tt : et.getTarget()) {
301                      ed.addTarget().setCode(tt.getCode()).setRelationship(combineRelationships(ts.getRelationship(), tt.getRelationship()));
302                    }
303                  }
304                }
305              }
306              if (ed.getTarget().isEmpty()) {
307                if (cumulative) {
308                  ed.getTarget().addAll(list);
309                } else {
310                  ed.setNoMap(true);
311                }
312              }
313            }
314            if (cumulative) {
315              for (SourceElementComponent et : gt.getElement()) {
316                if (!processed.contains(et)) {
317                  gd.addElement(et.copy());
318                }
319              }
320            }
321          }
322          Collections.sort(gt.getElement(), new ConceptMapElementSorter());
323          for (SourceElementComponent e: gt.getElement()) {
324            Collections.sort(e.getTarget(), new ConceptMapTargetElementSorter());
325          }
326        }
327      }
328    }
329    return res;
330  }
331
332  public static ConceptMapRelationship combineRelationships(ConceptMapRelationship rel1, ConceptMapRelationship rel2) {
333    switch (rel1) {
334    case EQUIVALENT:
335      return rel2;
336    case NOTRELATEDTO:
337      return ConceptMapRelationship.NOTRELATEDTO;
338    case NULL:
339      return null;
340    case RELATEDTO:
341      return rel2;
342    case SOURCEISBROADERTHANTARGET:
343      switch (rel2) {
344      case EQUIVALENT:
345        return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
346      case NOTRELATEDTO:
347        return ConceptMapRelationship.NOTRELATEDTO;
348      case NULL:
349        return null;
350      case RELATEDTO:
351        return ConceptMapRelationship.RELATEDTO;
352      case SOURCEISBROADERTHANTARGET:
353        return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
354      case SOURCEISNARROWERTHANTARGET:
355        return ConceptMapRelationship.RELATEDTO;
356      }
357    case SOURCEISNARROWERTHANTARGET:
358      switch (rel2) {
359      case EQUIVALENT:
360        return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
361      case NOTRELATEDTO:
362        return ConceptMapRelationship.NOTRELATEDTO;
363      case NULL:
364        return null;
365      case RELATEDTO:
366        return ConceptMapRelationship.RELATEDTO;
367      case SOURCEISBROADERTHANTARGET:
368        return ConceptMapRelationship.RELATEDTO;
369      case SOURCEISNARROWERTHANTARGET:
370        return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
371      }
372    }
373    return null;
374  }
375
376  public static boolean checkReciprocal(ConceptMap left, ConceptMap right, List<String> issues, boolean makeChanges) {
377    boolean changed = false;
378    if (!Base.compareDeep(left.getTargetScope(), right.getSourceScope(), true)) {
379      issues.add("scopes are not reciprocal: "+left.getTargetScope()+" vs "+right.getSourceScope());
380    }
381    if (!Base.compareDeep(left.getSourceScope(), right.getTargetScope(), true)) {
382      issues.add("scopes are not reciprocal: "+left.getSourceScope()+" vs "+right.getTargetScope());
383    }
384    for (ConceptMapGroupComponent gl : left.getGroup()) {
385      ConceptMapGroupComponent gr = findMatchingGroup(right.getGroup(), gl.getTarget(), gl.getSource());
386      if (gr == null) {
387        for (SourceElementComponent e : gl.getElement()) {
388          for (TargetElementComponent t : e.getTarget()) {
389            if (t.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
390              if (makeChanges) {
391                changed = true;
392                right.forceGroup(gl.getTarget(), gl.getSource()).getOrAddElement(t.getCode()).addTarget(e.getCode(), inverse(t.getRelationship()));
393              } else {
394                issues.add("left maps from "+gl.getSource()+"#"+e.getCode()+" to "+gl.getTarget()+"#"+t.getCode()+" but right has no matching reverse map");
395              }
396            } 
397          }
398        }
399      } else {
400        for (SourceElementComponent srcL : gl.getElement()) {
401          if (!srcL.getNoMap()) {
402            for (TargetElementComponent tgtL : srcL.getTarget()) {
403              List<ElementMappingPair> pairs = getMappings(gr, tgtL.getCode(), srcL.getCode());
404              if (tgtL.getRelationship() == null) {
405                issues.add("Left map has relationship "+srcL.getCode()+" with no relationship");
406              } else switch (tgtL.getRelationship()) {
407              case EQUIVALENT:
408                if (pairs.isEmpty()) {
409                  if (makeChanges) {
410                    changed = true;
411                    gr.getOrAddElement(tgtL.getCode()).addTarget(srcL.getCode(), ConceptMapRelationship.EQUIVALENT);
412                  } else {
413                    issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but there's no reverse relationship");
414                  }
415                } else for (ElementMappingPair pair : pairs) {
416                  if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
417                    issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
418                  }
419                }
420                break;
421              case RELATEDTO:
422                if (pairs.isEmpty()) {
423                  issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but there's no reverse relationship");
424                } else for (ElementMappingPair pair : pairs) {
425                  if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
426                    issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
427                  }
428                }
429                break;
430              case SOURCEISBROADERTHANTARGET:
431                if (pairs.isEmpty()) {
432                  issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but there's no reverse relationship");
433                } else for (ElementMappingPair pair : pairs) {
434                  if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
435                    issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
436                  }
437                }
438                break;
439              case SOURCEISNARROWERTHANTARGET:
440                if (pairs.isEmpty()) {
441                  issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but there's no reverse relationship");
442                } else for (ElementMappingPair pair : pairs) {
443                  if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
444                    issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
445                  }
446                }
447                break;
448              case NOTRELATEDTO:
449                for (ElementMappingPair pair : pairs) {
450                  if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
451                    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());
452                  }
453                }
454                break;
455              }
456            }
457          } else {
458            for (SourceElementComponent srcR : gr.getElement()) {
459              for (TargetElementComponent tgtR : srcR.getTarget()) {
460                if (srcL.getCode().equals(tgtR.getCode())) {
461                  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());
462                }
463              }
464            }
465          }
466        }
467      }
468    }
469    for (ConceptMapGroupComponent gr : right.getGroup()) {
470      ConceptMapGroupComponent gl = findMatchingGroup(left.getGroup(), gr.getTarget(), gr.getSource());
471      if (gl == null) {
472        for (SourceElementComponent e : gr.getElement()) {
473          for (TargetElementComponent t : e.getTarget()) {
474            if (t.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
475              if (makeChanges) {
476                changed = true;
477                left.forceGroup(gr.getTarget(), gr.getSource()).getOrAddElement(t.getCode()).addTarget(e.getCode(), inverse(t.getRelationship()));
478              } else {
479                issues.add("left maps from "+gr.getSource()+"#"+e.getCode()+" to "+gr.getTarget()+"#"+t.getCode()+" but right has no matching reverse map");
480              }
481            } 
482          }
483        }
484      } else {
485        for (SourceElementComponent srcR : gr.getElement()) {
486          if (!"CHECK!".equals(srcR.getCode())) {
487            if (!srcR.getNoMap()) {
488              for (TargetElementComponent tgtR : srcR.getTarget()) {
489                List<ElementMappingPair> pairs = getMappings(gl, tgtR.getCode(), srcR.getCode());
490                if (tgtR.getRelationship() == null) {
491                  issues.add("Right map has relationship "+srcR.getCode()+" with no relationship");
492                } else switch (tgtR.getRelationship()) {
493                case EQUIVALENT:
494                  if (pairs.isEmpty()) {
495                    if (makeChanges) {
496                      changed = true;
497                      gl.getOrAddElement(tgtR.getCode()).addTarget(srcR.getCode(), ConceptMapRelationship.EQUIVALENT);
498                    } else {
499                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but there's no reverse relationship");
500                    }
501                  } else for (ElementMappingPair pair : pairs) {
502                    if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
503                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
504                    }
505                  }
506                  break;
507                case RELATEDTO:
508                  if (pairs.isEmpty()) {
509                    issues.add("Right map says that "+srcR.getCode()+" is related to "+tgtR.getCode()+" but there's no reverse relationship");
510                  } else for (ElementMappingPair pair : pairs) {
511                    if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
512                      issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
513                    }
514                  }
515                  break;
516                case SOURCEISBROADERTHANTARGET:
517                  if (pairs.isEmpty()) {
518                    issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but there's no reverse relationship");
519                  } else for (ElementMappingPair pair : pairs) {
520                    if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
521                      issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
522                    }
523                  }
524                  break;
525                case SOURCEISNARROWERTHANTARGET:
526                  if (pairs.isEmpty()) {
527                    issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but there's no reverse relationship");
528                  } else for (ElementMappingPair pair : pairs) {
529                    if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
530                      issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
531                    }
532                  }
533                  break;
534                case NOTRELATEDTO:
535                  for (ElementMappingPair pair : pairs) {
536                    if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
537                      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());
538                    }
539                  }
540                  break;
541                }
542              }
543            } else {
544              for (SourceElementComponent srcL : gr.getElement()) {
545                for (TargetElementComponent tgtL : srcL.getTarget()) {
546                  if (srcR.getCode().equals(tgtL.getCode())) {
547                    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());
548                  }
549                }
550              }
551            }
552          }
553        }
554      }
555    }
556    return changed;
557  }
558
559  private static ConceptMapRelationship inverse(ConceptMapRelationship relationship) {
560    switch (relationship) {
561    case EQUIVALENT: return ConceptMapRelationship.EQUIVALENT;
562    case RELATEDTO: return ConceptMapRelationship.RELATEDTO;
563    case SOURCEISBROADERTHANTARGET: return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
564    case SOURCEISNARROWERTHANTARGET: return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
565    default: return null;
566    }
567  }
568
569  private static boolean hasActualMappings(ConceptMapGroupComponent gr) {
570    for (SourceElementComponent e : gr.getElement()) {
571      for (TargetElementComponent tgt : e.getTarget()) {
572        if (tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
573          return true;
574        }
575      }
576    }
577    return false;
578  }
579
580  private static List<ElementMappingPair> getMappings(ConceptMapGroupComponent g, String source, String target) {
581    List<ElementMappingPair> res = new ArrayList<ConceptMapUtilities.ElementMappingPair>();
582
583    for (SourceElementComponent src : g.getElement()) {
584      for (TargetElementComponent tgt : src.getTarget()) {
585        if (source.equals(src.getCode()) && target.equals(tgt.getCode())) {
586          res.add(new ElementMappingPair(src, tgt));
587        }
588      }
589    }
590    return res;
591  }
592
593  private static ConceptMapGroupComponent findMatchingGroup(List<ConceptMapGroupComponent> groups, String source, String target) {
594    for (ConceptMapGroupComponent g : groups) {
595      if (source.equals(g.getSource()) && target.equals(g.getTarget())) {
596        return g;
597      }
598    }
599    return null;
600  }
601
602  /** 
603   * 
604   * @param cmF
605   * @return true if all the maps simply map to the same code
606   */
607  public static boolean isUnityMap(ConceptMap cm) {
608    for (ConceptMapGroupComponent grp : cm.getGroup()) {
609      for (SourceElementComponent src : grp.getElement()) {
610        if (src.hasNoMap()) {
611          return false;
612        }
613        if (src.getTarget().size() != 1) {
614          return false;
615        }
616        if (src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.EQUIVALENT && src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.RELATEDTO) {
617          return false;
618        }
619        if (!src.getCode().equals(src.getTargetFirstRep().getCode())) {
620          return false;
621        }
622      }
623    }
624    return true;
625  }
626
627  public static int mapCount(ConceptMap cm) {
628    int i = 0;
629    for (ConceptMapGroupComponent grp : cm.getGroup()) {
630      for (SourceElementComponent src : grp.getElement()) {
631        i = i + src.getTarget().size();
632      }
633    }
634    return i;
635  }
636
637  public static Set<Coding> listCodesWithNoMappings(Set<Coding> codes, ConceptMap map) {
638    Set<Coding> res = new HashSet<>();
639    for (Coding c : codes) {
640      if (c != null && c.hasCode()) {
641        boolean found = false;
642        for (ConceptMapGroupComponent grp : map.getGroup()) {
643          if (matchesCoding(grp, c)) {
644            for (SourceElementComponent src : grp.getElement()) {
645              if (c.getCode().equals(src.getCode())) {
646                for (TargetElementComponent tgt : src.getTarget()) {
647                  if (tgt.getRelationship() == ConceptMapRelationship.RELATEDTO || tgt.getRelationship() == ConceptMapRelationship.EQUIVALENT || tgt.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
648                    found = true;                
649                  }
650                }
651              }
652            }
653          }
654        }
655        if (!found) {
656          res.add(c);
657        }
658      }
659    }    
660    return res;
661  }
662
663  private static boolean matchesCoding(ConceptMapGroupComponent grp, Coding code) {    
664    return code.getSystem().equals(grp.getSource()) || (code.getSystem()+"|"+code.getVersion()).equals(grp.getSource());
665  }
666
667  public static List<String> translateCode(String name, String defaultValue, ConceptMap... cmList) {
668    List<String> res = translateCode(name, cmList);
669    if (res.isEmpty()) {
670      res.add(defaultValue);
671    }
672    return res;
673  }
674  public static List<String> translateCode(String name, ConceptMap... cmList) {
675    List<String> res = new ArrayList<>();
676    res.add(name);
677    for (ConceptMap cm : cmList) {
678      res = translateCodes(res, cm);
679    }
680    return res;
681  }
682
683  private static List<String> translateCodes(List<String> codes, ConceptMap cm) {
684    List<String> res = new ArrayList<>();
685    for (ConceptMapGroupComponent g : cm.getGroup()) {
686      for (SourceElementComponent e : g.getElement()) {
687        if (Utilities.existsInList(e.getCode(), codes)) {
688          for (TargetElementComponent t : e.getTarget()) {
689            if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO || 
690                t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
691              res.add(t.getCode());
692            }
693          }
694        }
695      }
696    }
697    return res;
698  }
699
700  public static List<Coding> translateCoding(Coding code, ConceptMap... cmList) {
701    List<Coding> res = new ArrayList<>();
702    for (ConceptMap cm : cmList) {
703      res = translateCodings(res, cm);
704    }
705    return res;
706  }
707
708  private static List<Coding> translateCodings(List<Coding> codes, ConceptMap cm) {
709    List<Coding> res = new ArrayList<>();
710    for (ConceptMapGroupComponent g : cm.getGroup()) {
711      for (SourceElementComponent e : g.getElement()) {
712        if (hasCode(g.getSource(), e.getCode(), codes)) {
713          for (TargetElementComponent t : e.getTarget()) {
714            if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO || 
715                t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
716              res.add(new Coding().setSystem(g.getTarget()).setCode((t.getCode())));
717            }
718          }
719        }
720      }
721    }
722    return res;
723  }
724
725  private static boolean hasCode(String system, String code, List<Coding> codes) {
726    for (Coding c : codes) {
727      if (system.equals(c.getSystem()) && code.equals(c.getCode())) {
728        return true;
729      }
730    }
731    return false;
732  }
733
734  public static List<MappingTriple> getBySource(ConceptMap map, Coding c) {
735    List<MappingTriple> list = new ArrayList<>();
736    for (ConceptMapGroupComponent g : map.getGroup()) {
737      if (CanonicalType.matches(g.getSource(), c.getSystem(), c.getVersion())) {
738        for (SourceElementComponent e : g.getElement()) {
739          if (e.getCode().equals(c.getCode())) {
740            if (e.getNoMap()) {
741              list.add(new MappingTriple(g, e, null));
742            } else {
743              for (TargetElementComponent t : e.getTarget()) {
744                list.add(new MappingTriple(g, e, t));
745              }
746            }
747          }
748        }
749      }
750    }
751    return list;
752  }
753
754  public static List<MappingTriple> getByTarget(ConceptMap map, Coding c) {
755    List<MappingTriple> list = new ArrayList<>();
756    for (ConceptMapGroupComponent g : map.getGroup()) {
757      if (CanonicalType.matches(g.getTarget(), c.getSystem(), c.getVersion())) {
758        for (SourceElementComponent e : g.getElement()) {
759          for (TargetElementComponent t : e.getTarget()) {
760            if (t.getCode().equals(c.getCode())) {
761              list.add(new MappingTriple(g, e, t));
762            }
763          }
764        }
765      }
766    }
767    return list;
768  }
769
770}