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