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