001package org.hl7.fhir.r4.utils;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.lang3.NotImplementedException;
030import org.hl7.fhir.exceptions.DefinitionException;
031import org.hl7.fhir.exceptions.FHIRException;
032import org.hl7.fhir.exceptions.FHIRFormatError;
033import org.hl7.fhir.r4.conformance.ProfileUtilities;
034import org.hl7.fhir.r4.context.IWorkerContext;
035import org.hl7.fhir.r4.model.Base;
036import org.hl7.fhir.r4.model.BooleanType;
037import org.hl7.fhir.r4.model.CanonicalType;
038import org.hl7.fhir.r4.model.Coding;
039import org.hl7.fhir.r4.model.DateTimeType;
040import org.hl7.fhir.r4.model.DateType;
041import org.hl7.fhir.r4.model.DecimalType;
042import org.hl7.fhir.r4.model.Element;
043import org.hl7.fhir.r4.model.ElementDefinition;
044import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
045import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
046import org.hl7.fhir.r4.model.Enumeration;
047import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
048import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
049import org.hl7.fhir.r4.model.Factory;
050import org.hl7.fhir.r4.model.IntegerType;
051import org.hl7.fhir.r4.model.Quantity;
052import org.hl7.fhir.r4.model.Questionnaire;
053import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent;
054import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
055import org.hl7.fhir.r4.model.QuestionnaireResponse;
056import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent;
057import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus;
058import org.hl7.fhir.r4.model.Reference;
059import org.hl7.fhir.r4.model.Resource;
060import org.hl7.fhir.r4.model.StringType;
061import org.hl7.fhir.r4.model.StructureDefinition;
062import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
063import org.hl7.fhir.r4.model.TimeType;
064import org.hl7.fhir.r4.model.Type;
065import org.hl7.fhir.r4.model.UriType;
066import org.hl7.fhir.r4.model.ValueSet;
067import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
068import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
069import org.hl7.fhir.r4.terminologies.ValueSetExpander;
070import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
071import org.hl7.fhir.utilities.Utilities;
072
073
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
104
105/**
106 * This class takes a profile, and builds a questionnaire from it
107 * 
108 * If you then convert this questionnaire to a form using the 
109 * XMLTools form builder, and then take the QuestionnaireResponse 
110 * this creates, you can use QuestionnaireInstanceConvert to 
111 * build an instance the conforms to the profile
112 *  
113 * FHIR context: 
114 *   conceptLocator, codeSystems, valueSets, maps, client, profiles
115 * You don"t have to provide any of these, but 
116 * the more you provide, the better the conversion will be
117 * 
118 * @author Grahame
119 *
120 */
121public class QuestionnaireBuilder {
122
123  private static final int MaxListboxCodings = 20;
124  private IWorkerContext context;
125  private int lastid = 0;
126  private Resource resource;
127  private StructureDefinition profile;
128  private Questionnaire questionnaire;
129  private QuestionnaireResponse response;
130  private String questionnaireId;
131  private Factory factory = new Factory();
132  private Map<String, String> vsCache = new HashMap<String, String>();
133  private ValueSetExpander expander;
134
135  // sometimes, when this is used, the questionnaire is already build and cached, and we are
136  // processing the response. for technical reasons, we still go through the process, but
137  // we don't do the intensive parts of the work (save time)
138  private Questionnaire prebuiltQuestionnaire;
139
140  public QuestionnaireBuilder(IWorkerContext context) {
141    super();
142    this.context = context;
143  }
144
145  public Resource getReference() {
146    return resource;
147  }
148
149  public void setReference(Resource resource) {
150    this.resource = resource;
151  }
152
153  public StructureDefinition getProfile() {
154    return profile;
155  }
156
157  public void setProfile(StructureDefinition profile) {
158    this.profile = profile;
159  }
160
161  public Questionnaire getQuestionnaire() {
162    return questionnaire;
163  }
164
165  public void setQuestionnaire(Questionnaire questionnaire) {
166    this.questionnaire = questionnaire;
167  }
168
169  public QuestionnaireResponse getResponse() {
170    return response;
171  }
172
173  public void setResponse(QuestionnaireResponse response) {
174    this.response = response;
175  }
176
177  public String getQuestionnaireId() {
178    return questionnaireId;
179  }
180
181  public void setQuestionnaireId(String questionnaireId) {
182    this.questionnaireId = questionnaireId;
183  }
184
185  public Questionnaire getPrebuiltQuestionnaire() {
186    return prebuiltQuestionnaire;
187  }
188
189  public void setPrebuiltQuestionnaire(Questionnaire prebuiltQuestionnaire) {
190    this.prebuiltQuestionnaire = prebuiltQuestionnaire;
191  }
192
193  public ValueSetExpander getExpander() {
194    return expander;
195  }
196
197  public void setExpander(ValueSetExpander expander) {
198    this.expander = expander;
199  }
200
201  public void build() throws FHIRException {
202                if (profile == null)
203      throw new DefinitionException("QuestionnaireBuilder.build: no profile found");
204
205    if (resource != null)
206      if (!profile.getType().equals(resource.getResourceType().toString()))
207        throw new DefinitionException("Wrong Type");
208
209    if (prebuiltQuestionnaire != null)
210      questionnaire = prebuiltQuestionnaire;
211    else
212      questionnaire = new Questionnaire();
213    if (resource != null) 
214      response = new QuestionnaireResponse();
215    processMetadata();
216
217
218    List<ElementDefinition> list = new ArrayList<ElementDefinition>();
219    List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>();
220
221    if (resource != null)
222      answerGroups.addAll(response.getItem());
223    if (prebuiltQuestionnaire != null) {
224      // give it a fake group to build
225      Questionnaire.QuestionnaireItemComponent group = new Questionnaire.QuestionnaireItemComponent();
226      group.setType(QuestionnaireItemType.GROUP);
227      buildGroup(group, profile, profile.getSnapshot().getElement().get(0), list, answerGroups);
228    } else
229      buildGroup(questionnaire.getItem().get(0), profile, profile.getSnapshot().getElement().get(0), list, answerGroups);
230    //
231    //     NarrativeGenerator ngen = new NarrativeGenerator(context);
232    //     ngen.generate(result);
233    //
234    //    if FResponse <> nil then
235    //      FResponse.collapseAllContained;
236  }
237
238  private void processMetadata() {
239    // todo: can we derive a more informative identifier from the questionnaire if we have a profile
240    if (prebuiltQuestionnaire == null) {
241      questionnaire.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(questionnaireId);
242      questionnaire.setVersion(profile.getVersion());
243      questionnaire.setStatus(profile.getStatus());
244      questionnaire.setDate(profile.getDate());
245      questionnaire.setPublisher(profile.getPublisher());
246      Questionnaire.QuestionnaireItemComponent item = new Questionnaire.QuestionnaireItemComponent();
247      questionnaire.addItem(item);
248      item.getCode().addAll(profile.getKeyword());
249      questionnaire.setId(nextId("qs"));
250    }
251
252    if (response != null) {
253      // no identifier - this is transient
254      response.setQuestionnaire("#"+questionnaire.getId());
255      response.getContained().add(questionnaire);
256      response.setStatus(QuestionnaireResponseStatus.INPROGRESS);
257      QuestionnaireResponse.QuestionnaireResponseItemComponent item = new QuestionnaireResponse.QuestionnaireResponseItemComponent();
258      response.addItem(item);
259      item.setUserData("object", resource);
260    }
261
262  }
263
264  private String nextId(String prefix) {
265    lastid++;
266    return prefix+Integer.toString(lastid);
267  }
268
269  private void buildGroup(QuestionnaireItemComponent group, StructureDefinition profile, ElementDefinition element,
270      List<ElementDefinition> parents, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
271          group.setLinkId(element.getPath()); // todo: this will be wrong when we start slicing
272          group.setText(element.getShort()); // todo - may need to prepend the name tail... 
273          if (element.getComment() != null) {
274                Questionnaire.QuestionnaireItemComponent display = new Questionnaire.QuestionnaireItemComponent();
275                display.setType(QuestionnaireItemType.DISPLAY);
276                display.setText(element.getComment());
277                group.addItem(display);
278          }
279          group.setType(QuestionnaireItemType.GROUP);
280          ToolingExtensions.addFlyOver(group, element.getDefinition());
281    group.setRequired(element.getMin() > 0);
282    if (element.getMin() > 0)
283        ToolingExtensions.addMin(group, element.getMin());
284    group.setRepeats(!element.getMax().equals("1"));
285    if (!element.getMax().equals("*"))
286        ToolingExtensions.addMax(group, Integer.parseInt(element.getMax()));
287
288    for (org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) {
289      ag.setLinkId(group.getLinkId());
290      ag.setText(group.getText());
291    }
292
293    // now, we iterate the children
294    List<ElementDefinition> list = ProfileUtilities.getChildList(profile, element);
295    for (ElementDefinition child : list) {
296
297      if (!isExempt(element, child) && !parents.contains(child)) {
298                                List<ElementDefinition> nparents = new ArrayList<ElementDefinition>();
299        nparents.addAll(parents);
300        nparents.add(child);
301        QuestionnaireItemComponent childGroup = group.addItem();
302        childGroup.setType(QuestionnaireItemType.GROUP);
303
304        List<QuestionnaireResponse.QuestionnaireResponseItemComponent> nResponse = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>();
305        processExisting(child.getPath(), answerGroups, nResponse);
306        // if the element has a type, we add a question. else we add a group on the basis that
307        // it will have children of its own
308        if (child.getType().isEmpty() || isAbstractType(child.getType())) 
309          buildGroup(childGroup, profile, child, nparents, nResponse);
310        else if (isInlineDataType(child.getType()))
311          buildGroup(childGroup, profile, child, nparents, nResponse); // todo: get the right children for this one...
312        else
313          buildQuestion(childGroup, profile, child, child.getPath(), nResponse, parents);
314      }
315    }
316  }
317
318  private boolean isAbstractType(List<TypeRefComponent> type) {
319    return type.size() == 1 && (type.get(0).getWorkingCode().equals("Element") || type.get(0).getWorkingCode().equals("BackboneElement"));
320  }
321
322  private boolean isInlineDataType(List<TypeRefComponent> type) {
323    return type.size() == 1 && !Utilities.existsInList(type.get(0).getWorkingCode(), "code", "string", "id", "oid", "markdown", "uri", "boolean", "decimal", "dateTime", "date", "instant", "time", "CodeableConcept", "Period", "Ratio",
324        "HumanName", "Address", "ContactPoint", "Identifier", "integer", "positiveInt", "unsignedInt", "Coding", "Quantity",  "Count",  "Age",  "Duration", 
325        "Distance",  "Money", "Money", "Reference", "Duration", "base64Binary", "Attachment", "Age", "Range", "Timing", "Annotation", "SampledData", "Extension",
326        "SampledData", "Narrative", "Resource", "Meta", "url", "canonical");
327  }
328
329  private boolean isExempt(ElementDefinition element, ElementDefinition child) {
330    String n = tail(child.getPath());
331    String t = "";
332    if (!element.getType().isEmpty())
333      t =  element.getType().get(0).getWorkingCode();
334
335    // we don't generate questions for the base stuff in every element
336    if (t.equals("Resource")  && (n.equals("text") || n.equals("language") || n.equals("contained")))
337      return true;
338      // we don't generate questions for extensions
339    else if (n.equals("extension") || n.equals("modifierExtension")) {
340      if (child.getType().size() > 0 && !child.getType().get(0).hasProfile()) 
341      return false;
342      else
343        return true;
344    } else
345      return false;
346  }
347
348  private String tail(String path) {
349    return path.substring(path.lastIndexOf('.')+1);
350  }
351
352  private void processExisting(String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> nResponse) throws FHIRException {
353    // processing existing data
354    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) {
355      List<Base> children = ((Element) ag.getUserData("object")).listChildrenByName(tail(path));
356      for (Base child : children) {
357        if (child != null) {
358          QuestionnaireResponse.QuestionnaireResponseItemComponent ans = ag.addItem();
359          ans.setUserData("object", child);
360          nResponse.add(ans);
361        }
362      }
363    }
364  }
365
366  private void buildQuestion(QuestionnaireItemComponent group, StructureDefinition profile, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException {
367      group.setLinkId(path);
368
369      // in this context, we don't have any concepts to mark...
370      group.setText(element.getShort()); // prefix with name?
371      group.setRequired(element.getMin() > 0);
372            if (element.getMin() > 0)
373                ToolingExtensions.addMin(group, element.getMin());
374      group.setRepeats(!element.getMax().equals('1'));
375            if (!element.getMax().equals("*"))
376                ToolingExtensions.addMax(group, Integer.parseInt(element.getMax()));
377
378      for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) {
379        ag.setLinkId(group.getLinkId());
380        ag.setText(group.getText());
381      }
382
383      if (!Utilities.noString(element.getComment())) 
384        ToolingExtensions.addFlyOver(group, element.getDefinition()+" "+element.getComment());
385      else
386        ToolingExtensions.addFlyOver(group, element.getDefinition());
387
388      if (element.getType().size() > 1 || element.getType().get(0).getWorkingCode().equals("*")) {
389        List<TypeRefComponent> types = expandTypeList(element.getType());
390        Questionnaire.QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.CHOICE, element.getPath(), "_type", "type", null, makeTypeList(profile, types, element.getPath()));
391          for (TypeRefComponent t : types) {
392            Questionnaire.QuestionnaireItemComponent sub = q.addItem();
393            sub.setType(QuestionnaireItemType.GROUP);
394            sub.setLinkId(element.getPath()+"._"+t.getUserData("text"));
395            sub.setText((String) t.getUserData("text"));
396            // always optional, never repeats
397
398            List<QuestionnaireResponse.QuestionnaireResponseItemComponent> selected = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>();
399            selectTypes(profile, sub, t, answerGroups, selected);
400            processDataType(profile, sub, element, element.getPath()+"._"+t.getUserData("text"), t, selected, parents);
401          }
402      } else
403        // now we have to build the question panel for each different data type
404        processDataType(profile, group, element, element.getPath(), element.getType().get(0), answerGroups, parents);
405
406  }
407
408  private List<TypeRefComponent> expandTypeList(List<TypeRefComponent> types) {
409          List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
410    for (TypeRefComponent t : types) {
411            if (t.hasProfile())
412        result.add(t);
413            else if (t.getWorkingCode().equals("*")) {
414              result.add(new TypeRefComponent().setCode("integer"));
415              result.add(new TypeRefComponent().setCode("decimal"));
416              result.add(new TypeRefComponent().setCode("dateTime"));
417              result.add(new TypeRefComponent().setCode("date"));
418              result.add(new TypeRefComponent().setCode("instant"));
419              result.add(new TypeRefComponent().setCode("time"));
420              result.add(new TypeRefComponent().setCode("string"));
421              result.add(new TypeRefComponent().setCode("uri"));
422              result.add(new TypeRefComponent().setCode("boolean"));
423              result.add(new TypeRefComponent().setCode("Coding"));
424              result.add(new TypeRefComponent().setCode("CodeableConcept"));
425              result.add(new TypeRefComponent().setCode("Attachment"));
426              result.add(new TypeRefComponent().setCode("Identifier"));
427              result.add(new TypeRefComponent().setCode("Quantity"));
428              result.add(new TypeRefComponent().setCode("Range"));
429              result.add(new TypeRefComponent().setCode("Period"));
430              result.add(new TypeRefComponent().setCode("Ratio"));
431              result.add(new TypeRefComponent().setCode("HumanName"));
432              result.add(new TypeRefComponent().setCode("Address"));
433        result.add(new TypeRefComponent().setCode("ContactPoint"));
434        result.add(new TypeRefComponent().setCode("Timing"));
435              result.add(new TypeRefComponent().setCode("Reference"));
436      } else
437        result.add(t);
438    }
439    return result;
440  }
441
442  private ValueSet makeTypeList(StructureDefinition profile, List<TypeRefComponent> types, String path) {
443    ValueSet vs = new ValueSet();
444    vs.setName("Type options for "+path);
445    vs.setDescription(vs.present());
446          vs.setStatus(PublicationStatus.ACTIVE);
447    vs.setExpansion(new ValueSetExpansionComponent());
448    vs.getExpansion().setIdentifier(Factory.createUUID());
449    vs.getExpansion().setTimestampElement(DateTimeType.now());
450    for (TypeRefComponent t : types) {
451      if (t.hasTarget()) {
452        for (UriType u : t.getTargetProfile()) {
453                if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 
454                  ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains();
455              cc.setCode(u.getValue().substring(40));
456            cc.setSystem("http://hl7.org/fhir/resource-types");
457              cc.setDisplay(cc.getCode());
458                }
459        }
460      } else if (!t.hasProfile()) {
461        ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains();
462        cc.setCode(t.getWorkingCode());
463        cc.setDisplay(t.getWorkingCode());
464        cc.setSystem("http://hl7.org/fhir/data-types");
465      } else for (UriType u : t.getProfile()) {
466        ProfileUtilities pu = new ProfileUtilities(context, null, null);
467        StructureDefinition ps = pu.getProfile(profile, u.getValue());
468        if (ps != null) {
469          ValueSetExpansionContainsComponent cc = vs.getExpansion().addContains();
470                cc.setCode(u.getValue());
471          cc.setDisplay(ps.getType());
472          cc.setSystem("http://hl7.org/fhir/resource-types");
473        }
474      }
475    }
476
477    return vs;
478  }
479
480  private void selectTypes(StructureDefinition profile, QuestionnaireItemComponent sub, TypeRefComponent t, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> source, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> dest) throws FHIRFormatError {
481    List<QuestionnaireResponse.QuestionnaireResponseItemComponent> temp = new ArrayList<QuestionnaireResponse.QuestionnaireResponseItemComponent>();
482
483    for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : source)
484      if (instanceOf(t, (Element) g.getUserData("object"))) 
485        temp.add(g);
486    for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : temp)
487      source.remove(g);
488    for (QuestionnaireResponse.QuestionnaireResponseItemComponent g : temp) {
489      // 1st the answer:
490      assert(g.getItem().size() == 0); // it should be empty
491      QuestionnaireResponse.QuestionnaireResponseItemComponent q = g.addItem();
492      q.setLinkId(g.getLinkId()+"._type");
493      q.setText("type");
494
495      QuestionnaireResponseItemAnswerComponent a = q.addAnswer();
496      if (t.hasTarget()) {
497        for (UriType u : t.getTargetProfile()) {
498          if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
499            Coding cc = new Coding();
500            a.setValue(cc);
501            cc.setCode(u.getValue().substring(40));
502            cc.setSystem("http://hl7.org/fhir/resource-types");
503          }
504        }
505      } else {
506        Coding cc = new Coding();
507        a.setValue(cc);
508        ProfileUtilities pu = new ProfileUtilities(context, null, null);
509        StructureDefinition ps = null;
510        if (t.hasProfile())
511          ps = pu.getProfile(profile, t.getProfile().get(0).getValue());
512
513        if (ps != null) {
514          cc.setCode(t.getProfile().get(0).getValue());
515          cc.setSystem("http://hl7.org/fhir/resource-types");
516        } else {
517          cc.setCode(t.getWorkingCode());
518          cc.setSystem("http://hl7.org/fhir/data-types");
519        }
520      }
521
522      // 1st: create the subgroup
523      QuestionnaireResponse.QuestionnaireResponseItemComponent subg = a.addItem();
524      dest.add(subg);
525      subg.setLinkId(sub.getLinkId());
526      subg.setText(sub.getText());
527      subg.setUserData("object", g.getUserData("object"));
528    }
529  }
530
531  private boolean instanceOf(TypeRefComponent t, Element obj) {
532    if (t.getWorkingCode().equals("Reference")) {
533      if (!(obj instanceof Reference)) {
534        return false;
535      } else {
536        String url = ((Reference) obj).getReference();
537        // there are several problems here around profile matching. This process is degenerative, and there's probably nothing we can do to solve it
538        if (url.startsWith("http:") || url.startsWith("https:"))
539            return true;
540        else if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) 
541          return url.startsWith(t.getProfile().get(0).getValue().substring(40)+'/');
542        else
543          return true;
544      }
545    } else if (t.getWorkingCode().equals("Quantity")) {
546      return obj instanceof Quantity;
547    } else
548      throw new NotImplementedException("Not Done Yet");
549  }
550
551  private QuestionnaireItemComponent addQuestion(QuestionnaireItemComponent group, QuestionnaireItemType af, String path, String id, String name, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
552    return addQuestion(group, af, path, id, name, answerGroups, null);
553  }
554  
555  private QuestionnaireItemComponent addQuestion(QuestionnaireItemComponent group, QuestionnaireItemType af, String path, String id, String name, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, ValueSet vs) throws FHIRException {
556    QuestionnaireItemComponent result = group.addItem();
557    if (vs != null) {
558      if (vs.getExpansion() == null) {
559        result.setAnswerValueSet(vs.getUrl());
560        ToolingExtensions.addControl(result, "lookup"); 
561      } else {
562        if (Utilities.noString(vs.getId())) {
563          vs.setId(nextId("vs"));
564          questionnaire.getContained().add(vs);
565          vsCache.put(vs.getUrl(), vs.getId());
566          vs.setText(null);
567          vs.setCompose(null);
568          vs.getContact().clear();
569          vs.setPublisherElement(null);
570          vs.setCopyrightElement(null);
571        }
572        result.setAnswerValueSet("#"+vs.getId());
573      }
574    }
575  
576    result.setLinkId(path+'.'+id);
577    result.setText(name);
578    result.setType(af);
579    result.setRequired(false);
580    result.setRepeats(false);
581    if (id.endsWith("/1")) 
582      id = id.substring(0, id.length()-2);
583
584    if (answerGroups != null) {
585
586      for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups) {
587        List<Base> children = new ArrayList<Base>(); 
588
589        QuestionnaireResponse.QuestionnaireResponseItemComponent aq = null;
590        Element obj = (Element) ag.getUserData("object");
591        if (isPrimitive((TypeRefComponent) obj))
592          children.add(obj);
593        else if (obj instanceof Enumeration) {
594          String value = ((Enumeration) obj).toString();
595          children.add(new StringType(value));
596        } else
597          children = obj.listChildrenByName(id);
598
599        for (Base child: children) {
600          if (child != null) {
601            if (aq == null) {
602              aq = ag.addItem();
603              aq.setLinkId(result.getLinkId());
604              aq.setText(result.getText());
605            }
606            aq.addAnswer().setValue(convertType(child, af, vs, result.getLinkId()));
607          }
608        }
609      }
610    }
611    return result;
612  }
613
614  @SuppressWarnings("unchecked")
615  private Type convertType(Base value, QuestionnaireItemType af, ValueSet vs, String path) throws FHIRException {
616    switch (af) {
617      // simple cases
618    case BOOLEAN: if (value instanceof BooleanType) return (Type) value; break;
619    case DECIMAL: if (value instanceof DecimalType) return (Type) value; break;
620    case INTEGER: if (value instanceof IntegerType) return (Type) value; break;
621    case DATE: if (value instanceof DateType) return (Type) value; break;
622    case DATETIME: if (value instanceof DateTimeType) return (Type) value; break;
623    case TIME: if (value instanceof TimeType) return (Type) value; break;
624    case STRING:
625      if (value instanceof StringType) 
626        return (Type) value;
627      else if (value instanceof UriType) 
628        return new StringType(((UriType) value).asStringValue());
629                break;
630    case TEXT: if (value instanceof StringType) return (Type) value; break;
631    case QUANTITY: if (value instanceof  Quantity) return (Type) value; break;
632
633    // complex cases:
634    // ? QuestionnaireItemTypeAttachment: ...?
635    case CHOICE:
636    case OPENCHOICE :
637      if (value instanceof Coding)
638        return (Type) value;
639      else if (value instanceof Enumeration) { 
640        Coding cc = new Coding();
641        cc.setCode(((Enumeration<Enum<?>>) value).asStringValue());
642        cc.setSystem(getSystemForCode(vs, cc.getCode(), path));
643        return cc;
644      }  else if (value instanceof StringType) {
645        Coding cc = new Coding();
646        cc.setCode(((StringType) value).asStringValue());
647        cc.setSystem(getSystemForCode(vs, cc.getCode(), path));
648        return cc;
649      }
650                break;
651
652    case REFERENCE:
653      if (value instanceof Reference)
654        return (Type) value;
655      else if (value instanceof StringType) {
656        Reference r = new Reference();
657        r.setReference(((StringType) value).asStringValue());
658      }
659                break;
660         default:
661                break;
662    }
663
664    throw new FHIRException("Unable to convert from '"+value.getClass().toString()+"' for Answer Format "+af.toCode()+", path = "+path);
665  }
666
667  private String getSystemForCode(ValueSet vs, String code, String path) throws FHIRException {
668//    var
669//    i, q : integer;
670//  begin
671    String result = null;
672    if (vs == null) {
673      if (prebuiltQuestionnaire == null) 
674        throw new FHIRException("Logic error at path = "+path);
675      for (Resource r : prebuiltQuestionnaire.getContained()) {
676        if (r instanceof ValueSet) {
677          vs = (ValueSet) r;
678          if (vs.hasExpansion()) {
679            for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
680              if (c.getCode().equals(code)) {
681                  if (result == null)
682                    result = c.getSystem();
683                  else
684                    throw new FHIRException("Multiple matches in "+vs.getUrl()+" for code "+code+" at path = "+path);
685              }
686            }
687          }
688        }
689      }
690    }
691    
692    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
693      if (c.getCode().equals(code)) {
694        if (result == null)
695          result = c.getSystem();
696        else
697          throw new FHIRException("Multiple matches in "+vs.getUrl()+" for code "+code+" at path = "+path);
698      }
699    }
700    if (result != null)
701      return result;
702    throw new FHIRException("Unable to resolve code "+code+" at path = "+path);
703  }
704
705  private boolean isPrimitive(TypeRefComponent t) {
706    String code = t.getWorkingCode();
707    StructureDefinition sd = context.fetchTypeDefinition(code);
708    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
709  }
710
711  private void processDataType(StructureDefinition profile, QuestionnaireItemComponent group, ElementDefinition element, String path, TypeRefComponent t, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException {
712    String tc = t.getWorkingCode();
713    if (tc.equals("code"))
714      addCodeQuestions(group, element, path, answerGroups);
715    else if (Utilities.existsInList(tc, "string", "id", "oid", "uuid", "markdown"))
716      addStringQuestions(group, element, path, answerGroups);
717    else if (Utilities.existsInList(tc, "uri", "url", "canonical"))
718      addUriQuestions(group, element, path, answerGroups);
719    else if (tc.equals("boolean"))
720      addBooleanQuestions(group, element, path, answerGroups);
721    else if (tc.equals("decimal"))
722      addDecimalQuestions(group, element, path, answerGroups);
723    else if (tc.equals("dateTime") || tc.equals("date"))
724        addDateTimeQuestions(group, element, path, answerGroups);
725    else if (tc.equals("instant"))
726      addInstantQuestions(group, element, path, answerGroups);
727    else if (tc.equals("time"))
728      addTimeQuestions(group, element, path, answerGroups);
729    else if (tc.equals("CodeableConcept"))
730      addCodeableConceptQuestions(group, element, path, answerGroups);
731    else if (tc.equals("Period"))
732      addPeriodQuestions(group, element, path, answerGroups);
733    else if (tc.equals("Ratio"))
734      addRatioQuestions(group, element, path, answerGroups);
735    else if (tc.equals("HumanName"))
736      addHumanNameQuestions(group, element, path, answerGroups);
737    else if (tc.equals("Address"))
738      addAddressQuestions(group, element, path, answerGroups);
739    else if (tc.equals("ContactPoint"))
740      addContactPointQuestions(group, element, path, answerGroups);
741    else if (tc.equals("Identifier"))
742      addIdentifierQuestions(group, element, path, answerGroups);
743    else if (tc.equals("integer") || tc.equals("positiveInt") || tc.equals("unsignedInt") )
744      addIntegerQuestions(group, element, path, answerGroups);
745    else if (tc.equals("Coding"))
746      addCodingQuestions(group, element, path, answerGroups);
747    else if (Utilities.existsInList(tc, "Quantity", "Count", "Age", "Duration", "Distance", "Money"))
748      addQuantityQuestions(group, element, path, answerGroups);
749    else if (tc.equals("Money"))
750      addMoneyQuestions(group, element, path, answerGroups);
751    else if (tc.equals("Reference"))
752      addReferenceQuestions(group, element, path, t.getTargetProfile(), answerGroups);
753    else if (tc.equals("Duration"))
754      addDurationQuestions(group, element, path, answerGroups);
755    else if (tc.equals("base64Binary"))
756      addBinaryQuestions(group, element, path, answerGroups);
757    else if (tc.equals("Attachment"))
758      addAttachmentQuestions(group, element, path, answerGroups);
759    else if (tc.equals("Age"))
760      addAgeQuestions(group, element, path, answerGroups);
761    else if (tc.equals("Range"))
762      addRangeQuestions(group, element, path, answerGroups);
763    else if (tc.equals("Timing"))
764      addTimingQuestions(group, element, path, answerGroups);
765    else if (tc.equals("Annotation"))
766      addAnnotationQuestions(group, element, path, answerGroups);
767    else if (tc.equals("SampledData"))
768      addSampledDataQuestions(group, element, path, answerGroups);
769    else if (tc.equals("Extension")) {
770      if (t.hasProfile())
771        addExtensionQuestions(profile, group, element, path, t.getProfile().get(0).getValue(), answerGroups, parents);
772    } else if (tc.equals("SampledData"))
773      addSampledDataQuestions(group, element, path, answerGroups);
774    else if (!tc.equals("Narrative") && !tc.equals("Resource") && !tc.equals("Meta") && !tc.equals("Signature")) {
775      StructureDefinition sd = context.fetchTypeDefinition(tc);
776      if (sd == null)
777        throw new NotImplementedException("Unhandled Data Type: "+tc+" on element "+element.getPath());
778      buildGroup(group, sd, sd.getSnapshot().getElementFirstRep(), parents, answerGroups);
779    }
780  }
781
782  private void addCodeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
783    ToolingExtensions.addFhirType(group, "code");
784    ValueSet vs = resolveValueSet(null, element.hasBinding() ? element.getBinding() : null);
785    addQuestion(group, QuestionnaireItemType.CHOICE, path, "value", unCamelCase(tail(element.getPath())), answerGroups, vs);
786    group.setText(null);
787    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
788      ag.setText(null);
789  }
790
791  private String unCamelCase(String s) {
792    StringBuilder result = new StringBuilder();
793    
794      for (int i = 0; i < s.length(); i++) {
795        if (Character.isUpperCase(s.charAt(i))) 
796          result.append(' ');
797        result.append(s.charAt(i));
798      }
799      return result.toString().toLowerCase();
800  }
801
802  private void addStringQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
803    ToolingExtensions.addFhirType(group, "string");
804    addQuestion(group, QuestionnaireItemType.STRING, path, "value", group.getText(), answerGroups);
805          group.setText(null);
806    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
807      ag.setText(null);
808  }
809
810  private void addTimeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
811    ToolingExtensions.addFhirType(group, "time");
812    addQuestion(group, QuestionnaireItemType.TIME, path, "value", group.getText(), answerGroups);
813          group.setText(null);
814    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
815      ag.setText(null);
816  }
817
818  private void addUriQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
819    ToolingExtensions.addFhirType(group, "uri");
820    addQuestion(group, QuestionnaireItemType.STRING, path, "value", group.getText(), answerGroups);
821          group.setText(null);
822    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
823      ag.setText(null);
824  }
825
826  private void addBooleanQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
827    ToolingExtensions.addFhirType(group, "boolean");
828    addQuestion(group, QuestionnaireItemType.BOOLEAN, path, "value", group.getText(), answerGroups);
829          group.setText(null);
830    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
831      ag.setText(null);
832  }
833
834  private void addDecimalQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
835    ToolingExtensions.addFhirType(group, "decimal");
836    addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", group.getText(), answerGroups);
837          group.setText(null);
838    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
839      ag.setText(null);
840  }
841
842  private void addIntegerQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
843    ToolingExtensions.addFhirType(group, "integer");
844    addQuestion(group, QuestionnaireItemType.INTEGER, path, "value", group.getText(), answerGroups);
845          group.setText(null);
846    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
847      ag.setText(null);
848  }
849
850  private void addDateTimeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
851    ToolingExtensions.addFhirType(group, "datetime");
852    addQuestion(group, QuestionnaireItemType.DATETIME, path, "value", group.getText(), answerGroups);
853          group.setText(null);
854    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
855      ag.setText(null);
856  }
857
858  private void addInstantQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
859    ToolingExtensions.addFhirType(group, "instant");
860    addQuestion(group, QuestionnaireItemType.DATETIME, path, "value", group.getText(), answerGroups);
861          group.setText(null);
862    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
863      ag.setText(null);
864  }
865
866  private void addBinaryQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) {
867    ToolingExtensions.addFhirType(group, "binary");
868    // ? Lloyd: how to support binary content
869  }
870  
871  // Complex Types ---------------------------------------------------------------
872
873  private QuestionnaireItemType answerTypeForBinding(ElementDefinitionBindingComponent binding) {
874    if (binding == null) 
875      return QuestionnaireItemType.OPENCHOICE;
876    else if (binding.getStrength() != BindingStrength.REQUIRED) 
877      return QuestionnaireItemType.OPENCHOICE;
878    else
879      return QuestionnaireItemType.CHOICE;
880  }
881
882  private void addCodingQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
883    ToolingExtensions.addFhirType(group, "Coding");
884    addQuestion(group, answerTypeForBinding(element.hasBinding() ? element.getBinding() : null), path, "value", group.getText(), answerGroups, resolveValueSet(null, element.hasBinding() ? element.getBinding() : null));
885    group.setText(null);
886    for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
887      ag.setText(null);
888  }
889
890  private void addCodeableConceptQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
891    ToolingExtensions.addFhirType(group, "CodeableConcept");
892    addQuestion(group, answerTypeForBinding(element.hasBinding() ? element.getBinding() : null), path, "coding", "code:", answerGroups, resolveValueSet(null, element.hasBinding() ? element.getBinding() : null));
893    addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups);
894  }
895
896  private ValueSet makeAnyValueSet() {
897    // TODO Auto-generated method stub
898    return null;
899  }
900
901  private void addPeriodQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
902    ToolingExtensions.addFhirType(group, "Period");
903    addQuestion(group, QuestionnaireItemType.DATETIME, path, "low", "start:", answerGroups);
904    addQuestion(group, QuestionnaireItemType.DATETIME, path, "end", "end:", answerGroups);
905  }
906
907  private void addRatioQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
908    ToolingExtensions.addFhirType(group, "Ratio");
909    addQuestion(group, QuestionnaireItemType.DECIMAL, path, "numerator", "numerator:", answerGroups);
910    addQuestion(group, QuestionnaireItemType.DECIMAL, path, "denominator", "denominator:", answerGroups);
911    addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups);
912  }
913
914  private void addHumanNameQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
915    ToolingExtensions.addFhirType(group, "Name");
916    addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups);
917    addQuestion(group, QuestionnaireItemType.STRING, path, "family", "family:", answerGroups).setRepeats(true);
918    addQuestion(group, QuestionnaireItemType.STRING, path, "given", "given:", answerGroups).setRepeats(true);
919  }
920
921  private void addAddressQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
922    ToolingExtensions.addFhirType(group, "Address");
923    addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups);
924    addQuestion(group, QuestionnaireItemType.STRING, path, "line", "line:", answerGroups).setRepeats(true);
925    addQuestion(group, QuestionnaireItemType.STRING, path, "city", "city:", answerGroups);
926    addQuestion(group, QuestionnaireItemType.STRING, path, "state", "state:", answerGroups);
927    addQuestion(group, QuestionnaireItemType.STRING, path, "postalCode", "post code:", answerGroups);
928    addQuestion(group, QuestionnaireItemType.STRING, path, "country", "country:", answerGroups);
929    addQuestion(group, QuestionnaireItemType.CHOICE, path, "use", "use:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/address-use"));
930  }
931
932    private void addContactPointQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
933    ToolingExtensions.addFhirType(group, "ContactPoint");
934    addQuestion(group, QuestionnaireItemType.CHOICE, path, "system", "type:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/contact-point-system"));
935    addQuestion(group, QuestionnaireItemType.STRING, path, "value", "value:", answerGroups);
936    addQuestion(group, QuestionnaireItemType.CHOICE, path, "use", "use:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/contact-point-use"));
937    }
938    
939    private void addIdentifierQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
940      ToolingExtensions.addFhirType(group, "Identifier");
941      addQuestion(group, QuestionnaireItemType.STRING, path, "label", "label:", answerGroups);
942      addQuestion(group, QuestionnaireItemType.STRING, path, "system", "system:", answerGroups);
943      addQuestion(group, QuestionnaireItemType.STRING, path, "value", "value:", answerGroups);
944    }
945
946    private void addSimpleQuantityQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
947      ToolingExtensions.addFhirType(group, "Quantity");
948      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups);
949      addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups);
950      addQuestion(group, QuestionnaireItemType.STRING, path, "code", "coded units:", answerGroups);
951      addQuestion(group, QuestionnaireItemType.STRING, path, "system", "units system:", answerGroups);
952    }
953
954    private void addQuantityQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
955      ToolingExtensions.addFhirType(group, "Quantity");
956      addQuestion(group, QuestionnaireItemType.CHOICE, path, "comparator", "comp:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/quantity-comparator"));
957      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups);
958      addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups);
959      addQuestion(group, QuestionnaireItemType.STRING, path, "code", "coded units:", answerGroups);
960      addQuestion(group, QuestionnaireItemType.STRING, path, "system", "units system:", answerGroups);
961    }
962
963    private void addMoneyQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
964      ToolingExtensions.addFhirType(group, "Money");
965      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups);
966      addQuestion(group, QuestionnaireItemType.STRING, path, "currency", "currency:", answerGroups);
967  }
968
969    private void addAgeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
970      ToolingExtensions.addFhirType(group, "Age");
971      addQuestion(group, QuestionnaireItemType.CHOICE, path, "comparator", "comp:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/quantity-comparator"));
972      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups);
973      addQuestion(group, QuestionnaireItemType.CHOICE, path, "units", "units:", answerGroups, resolveValueSet("http://hl7.org/fhir/vs/duration-units"));
974    }
975
976    private void addDurationQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
977      ToolingExtensions.addFhirType(group, "Duration");
978      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "value", "value:", answerGroups);
979      addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups);
980    }
981
982    private void addAttachmentQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) {
983      ToolingExtensions.addFhirType(group, "Attachment");
984      //    raise Exception.Create("addAttachmentQuestions not Done Yet");
985    }
986
987    private void addRangeQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
988      ToolingExtensions.addFhirType(group, "Range");
989      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "low", "low:", answerGroups);
990      addQuestion(group, QuestionnaireItemType.DECIMAL, path, "high", "high:", answerGroups);
991      addQuestion(group, QuestionnaireItemType.STRING, path, "units", "units:", answerGroups);
992    }
993    
994    private void addSampledDataQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) {
995      ToolingExtensions.addFhirType(group, "SampledData");
996    }
997    
998    private void addTimingQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
999      ToolingExtensions.addFhirType(group, "Schedule");
1000      addQuestion(group, QuestionnaireItemType.STRING, path, "text", "text:", answerGroups);
1001      addQuestion(group, QuestionnaireItemType.DATETIME, path, "date", "date:", answerGroups);
1002      QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.REFERENCE, path, "author", "author:", answerGroups);
1003      ToolingExtensions.addAllowedResource(q, "Patient");
1004      ToolingExtensions.addAllowedResource(q, "Practitioner");
1005      ToolingExtensions.addAllowedResource(q, "RelatedPerson");
1006    }
1007    
1008    private void addAnnotationQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) {
1009      ToolingExtensions.addFhirType(group, "Annotation");
1010    }
1011  // Special Types ---------------------------------------------------------------
1012
1013    private void addReferenceQuestions(QuestionnaireItemComponent group, ElementDefinition element, String path, List<CanonicalType> profileURL, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups) throws FHIRException {
1014      //  var
1015      //    rn : String;
1016      //    i : integer;
1017      //    q : TFhirQuestionnaireGroupQuestion;
1018      ToolingExtensions.addFhirType(group, "Reference");
1019
1020      QuestionnaireItemComponent q = addQuestion(group, QuestionnaireItemType.REFERENCE, path, "value", group.getText(), answerGroups);
1021      group.setText(null);
1022      CommaSeparatedStringBuilder rn = new CommaSeparatedStringBuilder();
1023      for (UriType u : profileURL)
1024      if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/"))
1025        rn.append(u.getValue().substring(40));
1026      if (rn.length() == 0)
1027        ToolingExtensions.addReferenceFilter(q, "subject=$subj&patient=$subj&encounter=$encounter");
1028      else {
1029        ToolingExtensions.addAllowedResource(q, rn.toString());
1030        ToolingExtensions.addReferenceFilter(q, "subject=$subj&patient=$subj&encounter=$encounter");
1031      }
1032      for (QuestionnaireResponse.QuestionnaireResponseItemComponent ag : answerGroups)
1033        ag.setText(null);
1034    }
1035
1036
1037    private void addExtensionQuestions(StructureDefinition profile, QuestionnaireItemComponent group, ElementDefinition element, String path, String url, List<QuestionnaireResponse.QuestionnaireResponseItemComponent> answerGroups, List<ElementDefinition> parents) throws FHIRException { 
1038      // if this a  profiled extension, then we add it
1039        if (!Utilities.noString(url)) {
1040                StructureDefinition ed =  context.fetchResource(StructureDefinition.class, url);
1041                if (ed != null) {
1042          if (answerGroups.size() > 0)
1043            throw new NotImplementedException("Debug this");
1044                        buildQuestion(group, profile, ed.getSnapshot().getElement().get(0), path+".extension["+url+"]", answerGroups, parents);
1045        }
1046      }
1047    }
1048
1049    private ValueSet resolveValueSet(String url) {
1050//      if (prebuiltQuestionnaire != null)
1051        return null; // we don't do anything with value sets in this case
1052
1053//      if (vsCache.containsKey(url))
1054//        return (ValueSet) questionnaire.getContained(vsCache.get(url));
1055//      else {
1056//        ValueSet vs = context.findValueSet(url);
1057//        if (vs != null)
1058//          return expander.expand(vs, MaxListboxCodings, false);
1059//      }
1060//       
1061//       /*     on e: ETooCostly do
1062//            begin
1063//              result := TFhirValueSet.Create;
1064//              try
1065//                result.identifierST := ref.referenceST;
1066//                result.link;
1067//              finally
1068//                result.Free;
1069//              end;
1070//            end;
1071//            on e : Exception do
1072//              raise;
1073//          end;*/
1074//      }
1075    }
1076
1077    private ValueSet resolveValueSet(Object object, ElementDefinitionBindingComponent binding) {
1078      return null;
1079    }
1080
1081}