001package org.hl7.fhir.dstu3.terminologies;
002
003import static org.apache.commons.lang3.StringUtils.isNotBlank;
004
005import java.io.FileNotFoundException;
006import java.io.IOException;
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013import java.util.UUID;
014
015/*
016  Copyright (c) 2011+, HL7, Inc.
017  All rights reserved.
018  
019  Redistribution and use in source and binary forms, with or without modification, 
020  are permitted provided that the following conditions are met:
021    
022   * Redistributions of source code must retain the above copyright notice, this 
023     list of conditions and the following disclaimer.
024   * Redistributions in binary form must reproduce the above copyright notice, 
025     this list of conditions and the following disclaimer in the documentation 
026     and/or other materials provided with the distribution.
027   * Neither the name of HL7 nor the names of its contributors may be used to 
028     endorse or promote products derived from this software without specific 
029     prior written permission.
030  
031  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
032  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
033  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
034  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
035  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
036  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
037  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
038  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
039  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
040  POSSIBILITY OF SUCH DAMAGE.
041  
042 */
043
044
045
046import org.apache.commons.lang3.NotImplementedException;
047import org.hl7.fhir.dstu3.context.IWorkerContext;
048import org.hl7.fhir.dstu3.model.CodeSystem;
049import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
050import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
051import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionDesignationComponent;
052import org.hl7.fhir.dstu3.model.DateTimeType;
053import org.hl7.fhir.dstu3.model.ExpansionProfile;
054import org.hl7.fhir.dstu3.model.Factory;
055import org.hl7.fhir.dstu3.model.PrimitiveType;
056import org.hl7.fhir.dstu3.model.Type;
057import org.hl7.fhir.dstu3.model.UriType;
058import org.hl7.fhir.dstu3.model.ValueSet;
059import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent;
060import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceDesignationComponent;
061import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
062import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent;
063import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator;
064import org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent;
065import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
066import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
067import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionParameterComponent;
068import org.hl7.fhir.dstu3.utils.ToolingExtensions;
069import org.hl7.fhir.exceptions.FHIRException;
070import org.hl7.fhir.exceptions.FHIRFormatError;
071import org.hl7.fhir.exceptions.NoTerminologyServiceException;
072import org.hl7.fhir.exceptions.TerminologyServiceException;
073import org.hl7.fhir.utilities.Utilities;
074
075/*
076 * Copyright (c) 2011+, HL7, Inc
077 * All rights reserved.
078 *
079 * Redistribution and use in source and binary forms, with or without modification,
080 * are permitted provided that the following conditions are met:
081 *
082 * Redistributions of source code must retain the above copyright notice, this
083 * list of conditions and the following disclaimer.
084 * Redistributions in binary form must reproduce the above copyright notice,
085 * this list of conditions and the following disclaimer in the documentation
086 * and/or other materials provided with the distribution.
087 * Neither the name of HL7 nor the names of its contributors may be used to
088 * endorse or promote products derived from this software without specific
089 * prior written permission.
090 *
091 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
092 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
093 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
094 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
095 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
096 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
097 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
098 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
099 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
100 * POSSIBILITY OF SUCH DAMAGE.
101 *
102 */
103
104public class ValueSetExpanderSimple implements ValueSetExpander {
105
106  private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
107  private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
108  private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
109  private IWorkerContext context;
110  private boolean canBeHierarchy = true;
111  private Set<String> excludeKeys = new HashSet<String>();
112  private Set<String> excludeSystems = new HashSet<String>();
113  private ValueSetExpanderFactory factory;
114  private ValueSet focus;
115  private int maxExpansionSize = 500;
116
117  private int total;
118
119  public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) {
120    super();
121    this.context = context;
122    this.factory = factory;
123  }
124
125  public void setMaxExpansionSize(int theMaxExpansionSize) {
126    maxExpansionSize = theMaxExpansionSize;
127  }
128
129  private ValueSetExpansionContainsComponent addCode(String system, String code, String display, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations,
130                                                     ExpansionProfile profile, boolean isAbstract, boolean inactive, List<ValueSet> filters) {
131    if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
132      return null;
133    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
134    n.setSystem(system);
135    n.setCode(code);
136    if (isAbstract)
137      n.setAbstract(true);
138    if (inactive)
139      n.setInactive(true);
140
141    if (profile.getIncludeDesignations() && designations != null) {
142      for (ConceptDefinitionDesignationComponent t : designations) {
143        ToolingExtensions.addLanguageTranslation(n, t.getLanguage(), t.getValue());
144      }
145    }
146    ConceptDefinitionDesignationComponent t = profile.hasLanguage() ? getMatchingLang(designations, profile.getLanguage()) : null;
147    if (t == null)
148      n.setDisplay(display);
149    else
150      n.setDisplay(t.getValue());
151
152    String s = key(n);
153    if (map.containsKey(s) || excludeKeys.contains(s)) {
154      canBeHierarchy = false;
155    } else {
156      codes.add(n);
157      map.put(s, n);
158      total++;
159    }
160    if (canBeHierarchy && parent != null) {
161      parent.getContains().add(n);
162    } else {
163      roots.add(n);
164    }
165    return n;
166  }
167
168  private boolean filterContainsCode(List<ValueSet> filters, String system, String code) {
169    for (ValueSet vse : filters)
170      if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
171        return true;
172    return false;
173  }
174
175  private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) {
176    for (ValueSetExpansionContainsComponent cc : contains) {
177      if (system.equals(cc.getSystem()) && code.equals(cc.getCode()))
178        return true;
179      if (expansionContainsCode(cc.getContains(), system, code))
180        return true;
181    }
182    return false;
183  }
184
185  private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, String lang) {
186    for (ConceptDefinitionDesignationComponent t : list)
187      if (t.getLanguage().equals(lang))
188        return t;
189    for (ConceptDefinitionDesignationComponent t : list)
190      if (t.getLanguage().startsWith(lang))
191        return t;
192    return null;
193  }
194
195  private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, ExpansionProfile profile, List<ValueSet> filters)
196    throws FHIRException {
197    if (!CodeSystemUtilities.isDeprecated(cs, def)) {
198      ValueSetExpansionContainsComponent np = null;
199      boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
200      boolean inc = CodeSystemUtilities.isInactive(cs, def);
201      if (canBeHierarchy || !abs)
202        np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), profile, abs, inc, filters);
203      for (ConceptDefinitionComponent c : def.getConcept())
204        addCodeAndDescendents(cs, system, c, np, profile, filters);
205    } else
206      for (ConceptDefinitionComponent c : def.getConcept())
207        addCodeAndDescendents(cs, system, c, null, profile, filters);
208
209  }
210
211  private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, ExpansionProfile profile, List<ValueSet> filters) throws ETooCostly {
212    if (expand.getContains().size() > maxExpansionSize)
213      throw new ETooCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")");
214    for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
215      if (!existsInParams(params, p.getName(), p.getValue()))
216        params.add(p);
217    }
218
219    copyImportContains(expand.getContains(), null, profile, filters);
220  }
221
222  private void excludeCode(String theSystem, String theCode) {
223    ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
224    n.setSystem(theSystem);
225    n.setCode(theCode);
226    String s = key(n);
227    excludeKeys.add(s);
228  }
229
230  private void excludeCodes(ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params) throws TerminologyServiceException {
231    if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
232      excludeSystems.add(exc.getSystem());
233    }
234
235    if (exc.hasValueSet())
236      throw new Error("Processing Value set references in exclude is not yet done");
237    // importValueSet(imp.getValue(), params, profile);
238
239    CodeSystem cs = context.fetchCodeSystem(exc.getSystem());
240    if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) {
241      excludeCodes(context.expandVS(exc, false), params);
242      return;
243    }
244
245    for (ConceptReferenceComponent c : exc.getConcept()) {
246      excludeCode(exc.getSystem(), c.getCode());
247    }
248
249    if (exc.getFilter().size() > 0)
250      throw new NotImplementedException("not done yet");
251  }
252
253  private void excludeCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
254    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
255      excludeCode(c.getSystem(), c.getCode());
256    }
257  }
258
259  private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) {
260    for (ValueSetExpansionParameterComponent p : params) {
261      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
262        return true;
263    }
264    return false;
265  }
266
267  @Override
268  public ValueSetExpansionOutcome expand(ValueSet source, ExpansionProfile profile) {
269
270    if (profile == null)
271      profile = makeDefaultExpansion();
272    try {
273      focus = source.copy();
274      focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
275      focus.getExpansion().setTimestampElement(DateTimeType.now());
276      focus.getExpansion().setIdentifier(Factory.createUUID());
277      if (!profile.getUrl().startsWith("urn:uuid:"))
278        focus.getExpansion().addParameter().setName("profile").setValue(new UriType(profile.getUrl()));
279
280      if (source.hasCompose())
281        handleCompose(source.getCompose(), focus.getExpansion().getParameter(), profile);
282
283      if (canBeHierarchy) {
284        for (ValueSetExpansionContainsComponent c : roots) {
285          focus.getExpansion().getContains().add(c);
286        }
287      } else {
288        for (ValueSetExpansionContainsComponent c : codes) {
289          if (map.containsKey(key(c)) && !c.getAbstract()) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them
290            focus.getExpansion().getContains().add(c);
291            c.getContains().clear(); // make sure any hierarchy is wiped
292          }
293        }
294      }
295
296      if (total > 0) {
297        focus.getExpansion().setTotal(total);
298      }
299
300      return new ValueSetExpansionOutcome(focus);
301    } catch (NoTerminologyServiceException e) {
302      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
303      // that might fail too, but it might not, later.
304      return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage(), TerminologyServiceErrorClass.NOSERVICE);
305    } catch (RuntimeException e) {
306      // TODO: we should put something more specific instead of just Exception below, since
307      // it swallows bugs.. what would be expected to be caught there?
308      throw e;
309    } catch (Exception e) {
310      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
311      // that might fail too, but it might not, later.
312      return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
313    }
314  }
315
316  private ExpansionProfile makeDefaultExpansion() {
317    ExpansionProfile res = new ExpansionProfile();
318    res.setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase());
319    res.setExcludeNested(true);
320    res.setIncludeDesignations(false);
321    return res;
322  }
323
324  private String getCodeDisplay(CodeSystem cs, String code) throws TerminologyServiceException {
325    ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), code);
326    if (def == null)
327      throw new TerminologyServiceException("Unable to find code '" + code + "' in code system " + cs.getUrl());
328    return def.getDisplay();
329  }
330
331  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
332    for (ConceptDefinitionComponent c : clist) {
333      if (code.equals(c.getCode()))
334        return c;
335      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
336      if (v != null)
337        return v;
338    }
339    return null;
340  }
341
342  private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params, ExpansionProfile profile)
343    throws ETooCostly, FileNotFoundException, IOException, FHIRException {
344    // Exclude comes first because we build up a map of things to exclude
345    for (ConceptSetComponent inc : compose.getExclude())
346      excludeCodes(inc, params);
347    canBeHierarchy = !profile.getExcludeNested() && excludeKeys.isEmpty() && excludeSystems.isEmpty();
348    boolean first = true;
349    for (ConceptSetComponent inc : compose.getInclude()) {
350      if (first == true)
351        first = false;
352      else
353        canBeHierarchy = false;
354      includeCodes(inc, params, profile);
355    }
356
357  }
358
359  private ValueSet importValueSet(String value, List<ValueSetExpansionParameterComponent> params, ExpansionProfile profile)
360    throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
361    if (value == null)
362      throw new TerminologyServiceException("unable to find value set with no identity");
363    ValueSet vs = context.fetchResource(ValueSet.class, value);
364    if (vs == null)
365      throw new TerminologyServiceException("Unable to find imported value set " + value);
366    ValueSetExpansionOutcome vso = factory.getExpander().expand(vs, profile);
367    if (vso.getError() != null)
368      throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError());
369    if (vso.getService() != null)
370      throw new TerminologyServiceException("Unable to expand imported value set " + value);
371    if (vs.hasVersion())
372      if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion())))
373        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion())));
374    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
375      if (!existsInParams(params, p.getName(), p.getValue()))
376        params.add(p);
377    }
378    canBeHierarchy = false; // if we're importing a value set, we have to be combining, so we won't try for a hierarchy
379    return vso.getValueset();
380  }
381
382  private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, ExpansionProfile profile, List<ValueSet> filter) {
383    for (ValueSetExpansionContainsComponent c : list) {
384      ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, profile, c.getAbstract(), c.getInactive(), filter);
385      copyImportContains(c.getContains(), np, profile, filter);
386    }
387  }
388
389  private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params, ExpansionProfile profile) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
390    List<ValueSet> imports = new ArrayList<ValueSet>();
391    for (UriType imp : inc.getValueSet())
392      imports.add(importValueSet(imp.getValue(), params, profile));
393
394    if (!inc.hasSystem()) {
395      if (imports.isEmpty()) // though this is not supposed to be the case
396        return;
397      ValueSet base = imports.get(0);
398      imports.remove(0);
399      copyImportContains(base.getExpansion().getContains(), null, profile, imports);
400    } else {
401      CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
402      if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(inc.getSystem())) {
403        addCodes(context.expandVS(inc, canBeHierarchy), params, profile, imports);
404        return;
405      }
406
407      if (cs == null) {
408        if (context.isNoTerminologyServer())
409          throw new NoTerminologyServiceException("unable to find code system " + inc.getSystem().toString());
410        else
411          throw new TerminologyServiceException("unable to find code system " + inc.getSystem().toString());
412      }
413      if (cs.getContent() != CodeSystemContentMode.COMPLETE)
414        throw new TerminologyServiceException("Code system " + inc.getSystem().toString() + " is incomplete");
415      if (cs.hasVersion())
416        if (!existsInParams(params, "version", new UriType(cs.getUrl() + "|" + cs.getVersion())))
417          params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl() + "|" + cs.getVersion())));
418
419      if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
420        // special case - add all the code system
421        for (ConceptDefinitionComponent def : cs.getConcept()) {
422          addCodeAndDescendents(cs, inc.getSystem(), def, null, profile, imports);
423        }
424      }
425
426      if (!inc.getConcept().isEmpty()) {
427        canBeHierarchy = false;
428        for (ConceptReferenceComponent c : inc.getConcept()) {
429          addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay(), null, convertDesignations(c.getDesignation()), profile, false,
430            CodeSystemUtilities.isInactive(cs, c.getCode()), imports);
431        }
432      }
433      if (inc.getFilter().size() > 1) {
434        canBeHierarchy = false; // which will bt the case if we get around to supporting this
435        throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't done yet. But this shouldn't arise in non loinc and snomed value sets
436      }
437      if (inc.getFilter().size() == 1) {
438        ConceptSetFilterComponent fc = inc.getFilter().get(0);
439        if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
440          // special: all codes in the target code system under the value
441          ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
442          if (def == null)
443            throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
444          addCodeAndDescendents(cs, inc.getSystem(), def, null, profile, imports);
445        } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
446          // special: all codes in the target code system under the value
447          ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
448          if (def == null)
449            throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
450          for (ConceptDefinitionComponent c : def.getConcept())
451            addCodeAndDescendents(cs, inc.getSystem(), c, null, profile, imports);
452        } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
453          // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's diplsay is 'v'?
454          canBeHierarchy = false;
455          ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
456          if (def != null) {
457            if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
458              if (def.getDisplay().contains(fc.getValue())) {
459                addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), profile, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
460                  imports);
461              }
462            }
463          }
464        } else
465          throw new NotImplementedException("Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");
466      }
467    }
468  }
469
470  private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) {
471    List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>();
472    for (ConceptReferenceDesignationComponent t : list) {
473      ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent();
474      c.setLanguage(t.getLanguage());
475      c.setUse(t.getUse());
476      c.setValue(t.getValue());
477    }
478    return res;
479  }
480
481  private String key(String uri, String code) {
482    return "{" + uri + "}" + code;
483  }
484
485  private String key(ValueSetExpansionContainsComponent c) {
486    return key(c.getSystem(), c.getCode());
487  }
488
489}