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