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