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