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