001package org.hl7.fhir.instance.terminologies;
002
003/*
004Copyright (c) 2011+, HL7, Inc
005All rights reserved.
006
007Redistribution and use in source and binary forms, with or without modification, 
008are permitted provided that the following conditions are met:
009
010 * Redistributions of source code must retain the above copyright notice, this 
011   list of conditions and the following disclaimer.
012 * Redistributions in binary form must reproduce the above copyright notice, 
013   this list of conditions and the following disclaimer in the documentation 
014   and/or other materials provided with the distribution.
015 * Neither the name of HL7 nor the names of its contributors may be used to 
016   endorse or promote products derived from this software without specific 
017   prior written permission.
018
019THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028POSSIBILITY OF SUCH DAMAGE.
029
030*/
031
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import org.hl7.fhir.instance.model.DateTimeType;
038import org.hl7.fhir.instance.model.Factory;
039import org.hl7.fhir.instance.model.PrimitiveType;
040import org.hl7.fhir.instance.model.Type;
041import org.hl7.fhir.instance.model.UriType;
042import org.hl7.fhir.instance.model.ValueSet;
043import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
044import org.hl7.fhir.instance.model.ValueSet.ConceptReferenceComponent;
045import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
046import org.hl7.fhir.instance.model.ValueSet.ConceptSetFilterComponent;
047import org.hl7.fhir.instance.model.ValueSet.FilterOperator;
048import org.hl7.fhir.instance.model.ValueSet.ValueSetComposeComponent;
049import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
050import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionContainsComponent;
051import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionParameterComponent;
052import org.hl7.fhir.instance.utilities.Utilities;
053import org.hl7.fhir.instance.utils.IWorkerContext;
054import org.hl7.fhir.instance.utils.ToolingExtensions;
055
056public class ValueSetExpanderSimple implements ValueSetExpander {
057
058  private IWorkerContext context;
059  private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
060  private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
061  private ValueSet focus;
062
063        private ValueSetExpanderFactory factory;
064  
065  public ValueSetExpanderSimple(IWorkerContext context, ValueSetExpanderFactory factory) {
066    super();
067    this.context = context;
068    this.factory = factory;
069  }
070  
071  @Override
072  public ValueSetExpansionOutcome expand(ValueSet source) {
073
074    try {
075      focus = source.copy();
076      focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
077      focus.getExpansion().setTimestampElement(DateTimeType.now());
078      focus.getExpansion().setIdentifier(Factory.createUUID());
079
080      handleDefine(source, focus.getExpansion().getParameter());
081      if (source.hasCompose()) 
082        handleCompose(source.getCompose(), focus.getExpansion().getParameter());
083
084      for (ValueSetExpansionContainsComponent c : codes) {
085        if (map.containsKey(key(c))) {
086          focus.getExpansion().getContains().add(c);
087        }
088      }
089      return new ValueSetExpansionOutcome(focus, null);
090    } catch (Exception e) {
091      // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
092      // that might fail too, but it might not, later.
093      return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage());
094    }
095  }
096
097        private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params) throws Exception {
098        for (UriType imp : compose.getImport()) 
099                importValueSet(imp.getValue(), params);
100        for (ConceptSetComponent inc : compose.getInclude()) 
101                includeCodes(inc, params);
102        for (ConceptSetComponent inc : compose.getExclude()) 
103                excludeCodes(inc, params);
104
105  }
106
107        private void importValueSet(String value, List<ValueSetExpansionParameterComponent> params) throws Exception {
108          if (value == null)
109                throw new Exception("unable to find value set with no identity");
110          ValueSet vs = context.fetchResource(ValueSet.class, value);
111          if (vs == null)
112                        throw new Exception("Unable to find imported value set "+value);
113          ValueSetExpansionOutcome vso = factory.getExpander().expand(vs);
114          if (vso.getService() != null)
115      throw new Exception("Unable to expand imported value set "+value);
116    if (vs.hasVersion())
117      if (!existsInParams(params, "version", new UriType(vs.getUrl()+"?version="+vs.getVersion())))
118        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion())));
119    for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) {
120      if (!existsInParams(params, p.getName(), p.getValue()))
121          params.add(p);
122    }
123    
124          for (ValueSetExpansionContainsComponent c : vso.getValueset().getExpansion().getContains()) {
125                addCode(c.getSystem(), c.getCode(), c.getDisplay());
126          }       
127  }
128
129        private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) {
130    for (ValueSetExpansionParameterComponent p : params) {
131      if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
132        return true;
133    }
134    return false;
135  }
136
137  private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws Exception {
138          if (context.supportsSystem(inc.getSystem())) {
139        addCodes(context.expandVS(inc), params);
140      return;
141          }
142            
143          ValueSet cs = context.fetchCodeSystem(inc.getSystem());
144          if (cs == null)
145                throw new Exception("unable to find code system "+inc.getSystem().toString());
146          if (cs.hasVersion())
147      if (!existsInParams(params, "version", new UriType(cs.getUrl()+"?version="+cs.getVersion())))
148        params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl()+"?version="+cs.getVersion())));
149          if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
150            // special case - add all the code system
151            for (ConceptDefinitionComponent def : cs.getCodeSystem().getConcept()) {
152        addCodeAndDescendents(inc.getSystem(), def);
153            }
154          }
155            
156          for (ConceptReferenceComponent c : inc.getConcept()) {
157                addCode(inc.getSystem(), c.getCode(), Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay());
158          }
159          if (inc.getFilter().size() > 1)
160            throw new Exception("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
161    if (inc.getFilter().size() == 1) {
162            ConceptSetFilterComponent fc = inc.getFilter().get(0);
163                if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
164                        // special: all non-abstract codes in the target code system under the value
165                        ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), fc.getValue());
166                        if (def == null)
167                                throw new Exception("Code '"+fc.getValue()+"' not found in system '"+inc.getSystem()+"'");
168                        addCodeAndDescendents(inc.getSystem(), def);
169                } else
170                        throw new Exception("not done yet");
171          }
172  }
173
174        private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) throws Exception {
175          if (expand.getContains().size() > 500) 
176            throw new ETooCostly("Too many codes to display (>"+Integer.toString(expand.getContains().size())+")");
177    for (ValueSetExpansionParameterComponent p : expand.getParameter()) {
178      if (!existsInParams(params, p.getName(), p.getValue()))
179          params.add(p);
180    }
181          
182    for (ValueSetExpansionContainsComponent c : expand.getContains()) {
183      addCode(c.getSystem(), c.getCode(), c.getDisplay());
184    }   
185  }
186
187        private void addCodeAndDescendents(String system, ConceptDefinitionComponent def) {
188                if (!ToolingExtensions.hasDeprecated(def)) {  
189                        if (!def.hasAbstractElement() || !def.getAbstract())
190                                addCode(system, def.getCode(), def.getDisplay());
191                        for (ConceptDefinitionComponent c : def.getConcept()) 
192                                addCodeAndDescendents(system, c);
193                }
194  }
195
196        private void excludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params) throws Exception {
197          ValueSet cs = context.fetchCodeSystem(inc.getSystem().toString());
198          if (cs == null)
199                throw new Exception("unable to find value set "+inc.getSystem().toString());
200    if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
201      // special case - add all the code system
202//      for (ConceptDefinitionComponent def : cs.getDefine().getConcept()) {
203//!!!!        addCodeAndDescendents(inc.getSystem(), def);
204//      }
205    }
206      
207
208          for (ConceptReferenceComponent c : inc.getConcept()) {
209                // we don't need to check whether the codes are valid here- they can't have gotten into this list if they aren't valid
210                map.remove(key(inc.getSystem(), c.getCode()));
211          }
212          if (inc.getFilter().size() > 0)
213                throw new Exception("not done yet");
214  }
215
216        
217        private String getCodeDisplay(ValueSet cs, String code) throws Exception {
218                ConceptDefinitionComponent def = getConceptForCode(cs.getCodeSystem().getConcept(), code);
219                if (def == null)
220                        throw new Exception("Unable to find code '"+code+"' in code system "+cs.getCodeSystem().getSystem());
221                return def.getDisplay();
222  }
223
224        private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) {
225                for (ConceptDefinitionComponent c : clist) {
226                        if (code.equals(c.getCode()))
227                          return c;
228                        ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);   
229                        if (v != null)
230                          return v;
231                }
232                return null;
233  }
234        
235        private void handleDefine(ValueSet vs, List<ValueSetExpansionParameterComponent> list) {
236          if (vs.hasVersion())
237            list.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl()+"?version="+vs.getVersion())));
238          if (vs.hasCodeSystem()) {
239      // simple case: just generate the return
240        for (ConceptDefinitionComponent c : vs.getCodeSystem().getConcept()) 
241                addDefinedCode(vs, vs.getCodeSystem().getSystem(), c);
242        }
243  }
244
245        private String key(ValueSetExpansionContainsComponent c) {
246                return key(c.getSystem(), c.getCode());
247        }
248
249        private String key(String uri, String code) {
250                return "{"+uri+"}"+code;
251        }
252
253        private void addDefinedCode(ValueSet vs, String system, ConceptDefinitionComponent c) {
254                if (!ToolingExtensions.hasDeprecated(c)) { 
255
256                        if (!c.hasAbstractElement() || !c.getAbstract()) {
257                                addCode(system, c.getCode(), c.getDisplay());
258                        }
259                        for (ConceptDefinitionComponent g : c.getConcept()) 
260                                addDefinedCode(vs, vs.getCodeSystem().getSystem(), g);
261                }
262  }
263
264        private void addCode(String system, String code, String display) {
265                ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
266                n.setSystem(system);
267          n.setCode(code);
268          n.setDisplay(display);
269          String s = key(n);
270          if (!map.containsKey(s)) { 
271                codes.add(n);
272                map.put(s, n);
273          }
274  }
275
276  
277}