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