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