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