001package org.hl7.fhir.r4.conformance;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
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.Arrays;
026import java.util.Collections;
027import java.util.Comparator;
028import java.util.HashSet;
029import java.util.LinkedList;
030import java.util.List;
031
032import org.apache.commons.lang3.StringUtils;
033import org.apache.commons.lang3.tuple.ImmutablePair;
034import org.apache.commons.lang3.tuple.Pair;
035import org.hl7.fhir.exceptions.FHIRException;
036import org.hl7.fhir.r4.context.IWorkerContext;
037import org.hl7.fhir.r4.model.Constants;
038import org.hl7.fhir.r4.model.DomainResource;
039import org.hl7.fhir.r4.model.ElementDefinition;
040import org.hl7.fhir.r4.model.Enumerations;
041import org.hl7.fhir.r4.model.StructureDefinition;
042import org.hl7.fhir.r4.model.Type;
043import org.hl7.fhir.r4.model.ValueSet;
044import org.hl7.fhir.r4.terminologies.ValueSetExpander;
045import org.hl7.fhir.r4.utils.ToolingExtensions;
046import org.stringtemplate.v4.ST;
047
048public class ShExGenerator {
049
050  public enum HTMLLinkPolicy {
051    NONE, EXTERNAL, INTERNAL
052  }
053  public boolean doDatatypes = true;                 // add data types
054  public boolean withComments = true;                // include comments
055  public boolean completeModel = false;              // doing complete build (fhir.shex)
056
057
058  private static String SHEX_TEMPLATE = "$header$\n\n" +
059          "$shapeDefinitions$";
060
061  // A header is a list of prefixes, a base declaration and a start node
062  private static String FHIR = "http://hl7.org/fhir/";
063  private static String FHIR_VS = FHIR + "ValueSet/";
064  private static String HEADER_TEMPLATE =
065          "PREFIX fhir: <$fhir$> \n" +
066                  "PREFIX fhirvs: <$fhirvs$>\n" +
067                  "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" +
068                  "BASE <http://hl7.org/fhir/shape/>\n$start$";
069
070  // Start template for single (open) entry
071  private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n";
072
073  // Start template for complete (closed) model
074  private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n";
075
076  private static String ALL_TEMPLATE = "\n<All> $all_entries$\n";
077
078  private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)";
079
080
081  // Shape Definition
082  //      the shape name
083  //      an optional resource declaration (type + treeRoot)
084  //      the list of element declarations
085  //      an optional index element (for appearances inside ordered lists)
086  private static String SHAPE_DEFINITION_TEMPLATE =
087          "$comment$\n<$id$> CLOSED {\n    $resourceDecl$" +
088                  "\n    $elements$" +
089                  "\n    fhir:index xsd:integer?                 # Relative position in a list\n}\n";
090
091  // Resource Definition
092  //      an open shape of type Resource.  Used when completeModel = false.
093  private static String RESOURCE_SHAPE_TEMPLATE =
094          "$comment$\n<Resource> {a .+;" +
095                  "\n    $elements$" +
096                  "\n    fhir:index xsd:integer?" +
097                  "\n}\n";
098
099  // If we have knowledge of all of the possible resources available to us (completeModel = true), we can build
100  // a model of all possible resources.
101  private static String COMPLETE_RESOURCE_TEMPLATE =
102          "<Resource>  @<$resources$>" +
103                  "\n\n";
104
105  // Resource Declaration
106  //      a type node
107  //      an optional treeRoot declaration (identifies the entry point)
108  private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$];$root$";
109
110  // Root Declaration.
111  private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;";
112
113  // Element
114  //    a predicate, type and cardinality triple
115  private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$";
116  private static int COMMENT_COL = 40;
117  private static int MAX_CHARS = 35;
118  private static int MIN_COMMENT_SEP = 2;
119
120  // Inner Shape Definition
121  private static String INNER_SHAPE_TEMPLATE = "($comment$\n    $defn$\n)$card$";
122
123  // Simple Element
124  //    a shape reference
125  private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$";
126
127  // Value Set Element
128  private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:value @$vsn$}";
129
130  // Fixed Value Template
131  private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}";
132
133  // A primitive element definition
134  //    the actual type reference
135  private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$";
136
137  // Facets
138  private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$";
139  private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$";
140  private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$";
141  private static String PATTERN_TEMPLATE = " PATTERN \"$val$\"";
142
143  // A choice of alternative shape definitions
144  //  rendered as an inner anonymous shape
145  private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n(   $altEntries$\n)$card$";
146
147  // A typed reference definition
148  private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>";
149
150  // What we emit for an xhtml
151  private static String XHTML_TYPE_TEMPLATE = "xsd:string";
152
153  // Additional type for Coding
154  private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;";
155
156  // Additional type for CodedConcept
157  private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;";
158
159  // Untyped resource has the extra link entry
160  private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;";
161
162  // Extension template
163  // No longer used -- we emit the actual definition
164//  private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" +
165//          "\n    fhir:index xsd:integer?" +
166//          "\n}\n";
167
168  // A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape
169  private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" +
170          "\n    fhir:Element.id @<id>?;" +
171          "\n    fhir:Element.extension @<Extension>*;" +
172          "\n    fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" +
173          "\n    fhir:Reference.reference @<string>?;" +
174          "\n    fhir:Reference.display @<string>?;" +
175          "\n    fhir:index xsd:integer?" +
176          "\n}";
177
178  private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" +
179          "\n    a [fhir:$refType$];" +
180          "\n    fhir:nodeRole [fhir:treeRoot]?" +
181          "\n}";
182
183  // A value set definition
184  private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n";
185
186
187  /**
188   * this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc
189   */
190  private IWorkerContext context;
191
192  /**
193   * innerTypes -- inner complex types.  Currently flattened in ShEx (doesn't have to be, btw)
194   * emittedInnerTypes -- set of inner types that have been generated
195   * datatypes, emittedDatatypes -- types used in the definition, types that have been generated
196   * references -- Reference types (Patient, Specimen, etc)
197   * uniq_structures -- set of structures on the to be generated list...
198   * doDataTypes -- whether or not to emit the data types.
199   */
200  private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes;
201  private HashSet<String> datatypes, emittedDatatypes;
202  private HashSet<String> references;
203  private LinkedList<StructureDefinition> uniq_structures;
204  private HashSet<String> uniq_structure_urls;
205  private HashSet<ValueSet> required_value_sets;
206  private HashSet<String> known_resources;          // Used when generating a full definition
207
208  public ShExGenerator(IWorkerContext context) {
209    super();
210    this.context = context;
211    innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
212    emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
213    datatypes = new HashSet<String>();
214    emittedDatatypes = new HashSet<String>();
215    references = new HashSet<String>();
216    required_value_sets = new HashSet<ValueSet>();
217    known_resources = new HashSet<String>();
218  }
219
220  public String generate(HTMLLinkPolicy links, StructureDefinition structure) {
221    List<StructureDefinition> list = new ArrayList<StructureDefinition>();
222    list.add(structure);
223    innerTypes.clear();
224    emittedInnerTypes.clear();
225    datatypes.clear();
226    emittedDatatypes.clear();
227    references.clear();
228    required_value_sets.clear();
229    known_resources.clear();
230    return generate(links, list);
231  }
232
233  public class SortById implements Comparator<StructureDefinition> {
234
235    @Override
236    public int compare(StructureDefinition arg0, StructureDefinition arg1) {
237      return arg0.getId().compareTo(arg1.getId());
238    }
239
240  }
241
242  private ST tmplt(String template) {
243    return new ST(template, '$', '$');
244  }
245
246  /**
247   * this is called externally to generate a set of structures to a single ShEx file
248   * generally, it will be called with a single structure, or a long list of structures (all of them)
249   *
250   * @param links HTML link rendering policy
251   * @param structures list of structure definitions to render
252   * @return ShEx definition of structures
253   */
254  public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) {
255
256    ST shex_def = tmplt(SHEX_TEMPLATE);
257    String start_cmd;
258    if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE))
259//            || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE))
260      start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() :
261              tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render();
262    else
263      start_cmd = "";
264    shex_def.add("header", tmplt(HEADER_TEMPLATE).
265            add("start", start_cmd).
266            add("fhir", FHIR).
267            add("fhirvs", FHIR_VS).render());
268
269    Collections.sort(structures, new SortById());
270    StringBuilder shapeDefinitions = new StringBuilder();
271
272    // For unknown reasons, the list of structures carries duplicates.  We remove them
273    // Also, it is possible for the same sd to have multiple hashes...
274    uniq_structures = new LinkedList<StructureDefinition>();
275    uniq_structure_urls = new HashSet<String>();
276    for (StructureDefinition sd : structures) {
277      if (!uniq_structure_urls.contains(sd.getUrl())) {
278        uniq_structures.add(sd);
279        uniq_structure_urls.add(sd.getUrl());
280      }
281    }
282
283
284    for (StructureDefinition sd : uniq_structures) {
285      shapeDefinitions.append(genShapeDefinition(sd, true));
286    }
287
288    shapeDefinitions.append(emitInnerTypes());
289    if(doDatatypes) {
290      shapeDefinitions.append("\n#---------------------- Data Types -------------------\n");
291      while (emittedDatatypes.size() < datatypes.size() ||
292              emittedInnerTypes.size() < innerTypes.size()) {
293        shapeDefinitions.append(emitDataTypes());
294        shapeDefinitions.append(emitInnerTypes());
295      }
296    }
297
298    shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n");
299    for(String r: references) {
300      shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
301      if (!"Resource".equals(r) && !known_resources.contains(r))
302        shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
303    }
304    shex_def.add("shapeDefinitions", shapeDefinitions);
305
306    if(completeModel && known_resources.size() > 0) {
307      shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE)
308              .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render());
309      List<String> all_entries = new ArrayList<String>();
310      for(String kr: known_resources)
311        all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render());
312      shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE)
313              .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render());
314    }
315
316    shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n");
317    for(ValueSet vs: required_value_sets)
318      shapeDefinitions.append("\n").append(genValueSet(vs));
319    return shex_def.render();
320  }
321
322
323  /**
324   * Emit a ShEx definition for the supplied StructureDefinition
325   * @param sd Structure definition to emit
326   * @param top_level True means outermost type, False means recursively called
327   * @return ShEx definition
328   */
329  private String genShapeDefinition(StructureDefinition sd, boolean top_level) {
330    // xhtml is treated as an atom
331    if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName())))
332      return "";
333
334    ST shape_defn;
335    // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel)
336    if("Resource".equals(sd.getName())) {
337      shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE);
338      known_resources.add(sd.getName());
339    } else {
340      shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", sd.getId());
341      if (sd.getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) {
342//              || sd.getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) {
343        known_resources.add(sd.getName());
344        ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE).
345                add("id", sd.getId()).
346                add("root", tmplt(ROOT_TEMPLATE));
347//                add("root", top_level ? tmplt(ROOT_TEMPLATE) : "");
348        shape_defn.add("resourceDecl", resource_decl.render());
349      } else {
350        shape_defn.add("resourceDecl", "");
351      }
352    }
353
354    // Generate the defining elements
355    List<String> elements = new ArrayList<String>();
356
357    // Add the additional entries for special types
358    String sdn = sd.getName();
359    if (sdn.equals("Coding"))
360      elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render());
361    else if (sdn.equals("CodeableConcept"))
362      elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render());
363    else if (sdn.equals("Reference"))
364      elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render());
365//    else if (sdn.equals("Extension"))
366//      return tmplt(EXTENSION_TEMPLATE).render();
367
368    String root_comment = null;
369    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
370      if(!ed.getPath().contains("."))
371        root_comment = ed.getShort();
372      else if (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) {
373        elements.add(genElementDefinition(sd, ed));
374      }
375    }
376    shape_defn.add("elements", StringUtils.join(elements, "\n"));
377    shape_defn.add("comment", root_comment == null? " " : "# " + root_comment);
378    return shape_defn.render();
379  }
380
381
382  /**
383   * Generate a flattened definition for the inner types
384   * @return stringified inner type definitions
385   */
386  private String emitInnerTypes() {
387    StringBuilder itDefs = new StringBuilder();
388    while(emittedInnerTypes.size() < innerTypes.size()) {
389      for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) {
390        if (!emittedInnerTypes.contains(it)) {
391          itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight()));
392          emittedInnerTypes.add(it);
393        }
394      }
395    }
396    return itDefs.toString();
397  }
398
399  /**
400   * Generate a shape definition for the current set of datatypes
401   * @return stringified data type definitions
402   */
403  private String emitDataTypes() {
404    StringBuilder dtDefs = new StringBuilder();
405    while (emittedDatatypes.size() < datatypes.size()) {
406      for (String dt : new HashSet<String>(datatypes)) {
407        if (!emittedDatatypes.contains(dt)) {
408          StructureDefinition sd = context.fetchResource(StructureDefinition.class,
409              ProfileUtilities.sdNs(dt, null));
410          // TODO: Figure out why the line below doesn't work
411          // if (sd != null && !uniq_structures.contains(sd))
412          if(sd != null && !uniq_structure_urls.contains(sd.getUrl()))
413            dtDefs.append("\n").append(genShapeDefinition(sd, false));
414          emittedDatatypes.add(dt);
415        }
416      }
417    }
418    return dtDefs.toString();
419  }
420
421  private ArrayList<String> split_text(String text, int max_col) {
422    int pos = 0;
423    ArrayList<String> rval = new ArrayList<String>();
424    if (text.length() <= max_col) {
425      rval.add(text);
426    } else {
427      String[] words = text.split(" ");
428      int word_idx = 0;
429      while(word_idx < words.length) {
430        StringBuilder accum = new StringBuilder();
431        while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col)
432          accum.append(words[word_idx++] + " ");
433        if (accum.length() == 0) {
434          accum.append(words[word_idx].substring(0, max_col - 3) + "-");
435          words[word_idx] = words[word_idx].substring(max_col - 3);
436        }
437        rval.add(accum.toString());
438        accum = new StringBuilder();
439      }
440    }
441    return rval;
442  }
443
444  private void addComment(ST tmplt, ElementDefinition ed) {
445    if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) {
446      int nspaces;
447      char[] sep;
448      nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP);
449      tmplt.remove("comment");
450      sep = new char[nspaces];
451      Arrays.fill(sep, ' ');
452      ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS);
453      StringBuilder comment = new StringBuilder("# ");
454      char[] indent = new char[COMMENT_COL];
455      Arrays.fill(indent, ' ');
456      for(int i = 0; i < comment_lines.size();) {
457        comment.append(comment_lines.get(i++));
458        if(i < comment_lines.size())
459          comment.append("\n" + new String(indent) + "# ");
460      }
461      tmplt.add("comment", new String(sep) + comment.toString());
462    } else {
463      tmplt.add("comment", " ");
464    }
465  }
466
467
468  /**
469   * Generate a ShEx element definition
470   * @param sd Containing structure definition
471   * @param ed Containing element definition
472   * @return ShEx definition
473   */
474  private String genElementDefinition(StructureDefinition sd, ElementDefinition ed) {
475    String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
476    String shortId = id.substring(id.lastIndexOf(".") + 1);
477    String defn;
478    ST element_def;
479    String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";";
480
481    if(id.endsWith("[x]")) {
482      element_def = ed.getType().size() > 1? tmplt(INNER_SHAPE_TEMPLATE) : tmplt(ELEMENT_TEMPLATE);
483      element_def.add("id", "");
484    } else {
485      element_def = tmplt(ELEMENT_TEMPLATE);
486      element_def.add("id", "fhir:" + (id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id) + " ");
487    }
488
489    List<ElementDefinition> children = ProfileUtilities.getChildList(sd, ed);
490    if (children.size() > 0) {
491      innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
492      defn = simpleElement(sd, ed, id);
493    } else if(id.endsWith("[x]")) {
494      defn = genChoiceTypes(sd, ed, id);
495    }
496    else if (ed.getType().size() == 1) {
497      // Single entry
498      defn = genTypeRef(sd, ed, id, ed.getType().get(0));
499    } else if (ed.getContentReference() != null) {
500      // Reference to another element
501      String ref = ed.getContentReference();
502      if(!ref.startsWith("#"))
503        throw new AssertionError("Not equipped to deal with absolute path references: " + ref);
504      String refPath = null;
505      for(ElementDefinition ed1: sd.getSnapshot().getElement()) {
506        if(ed1.getId() != null && ed1.getId().equals(ref.substring(1))) {
507          refPath = ed1.getPath();
508          break;
509        }
510      }
511      if(refPath == null)
512        throw new AssertionError("Reference path not found: " + ref);
513      // String typ = id.substring(0, id.indexOf(".") + 1) + ed.getContentReference().substring(1);
514      defn = simpleElement(sd, ed, refPath);
515    } else if(id.endsWith("[x]")) {
516      defn = genChoiceTypes(sd, ed, id);
517    } else {
518      // TODO: Refactoring required here
519      element_def = genAlternativeTypes(ed, id, shortId);
520      element_def.add("id", id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id);
521      element_def.add("card", card);
522      addComment(element_def, ed);
523      return element_def.render();
524    }
525    element_def.add("defn", defn);
526    element_def.add("card", card);
527    addComment(element_def, ed);
528    return element_def.render();
529  }
530
531  /**
532   * Generate a type reference and optional value set definition
533   * @param sd Containing StructureDefinition
534   * @param ed Element being defined
535   * @param typ Element type
536   * @return Type definition
537   */
538  private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) {
539    String addldef = "";
540    ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding();
541    if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) {
542      ValueSet vs = resolveBindingReference(sd, binding.getValueSet());
543      if (vs != null) {
544        addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render();
545        required_value_sets.add(vs);
546      }
547    }
548    // TODO: check whether value sets and fixed are mutually exclusive
549    if(ed.hasFixed()) {
550      addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render();
551    }
552    return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render();
553  }
554
555  private String vsprefix(String uri) {
556    if(uri.startsWith(FHIR_VS))
557      return "fhirvs:" + uri.replace(FHIR_VS, "");
558    return "<" + uri + ">";
559  }
560
561  /**
562   * Generate a type reference
563   * @param sd Containing structure definition
564   * @param ed Containing element definition
565   * @param id Element id
566   * @param typ Element type
567   * @return Type reference string
568   */
569  private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) {
570
571    if(typ.hasProfile()) {
572      if(typ.getWorkingCode().equals("Reference"))
573        return genReference("", typ);
574      else if(ProfileUtilities.getChildList(sd, ed).size() > 0) {
575        // inline anonymous type - give it a name and factor it out
576        innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
577        return simpleElement(sd, ed, id);
578      }
579      else {
580        String ref = getTypeName(typ);
581        datatypes.add(ref);
582        return simpleElement(sd, ed, ref);
583      }
584
585    } else if (typ.getWorkingCode().startsWith(Constants.NS_SYSTEM_TYPE)) {
586      String xt = typ.getWorkingCode();
587      // TODO: Remove the next line when the type of token gets switched to string
588      // TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int)
589      ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ",
590              xt.replace("xsd:token", "xsd:string").replace("xsd:int", "xsd:integer"));
591      StringBuilder facets =  new StringBuilder();
592      if(ed.hasMinValue()) {
593        Type mv = ed.getMinValue();
594        facets.append(tmplt(MINVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
595      }
596      if(ed.hasMaxValue()) {
597        Type mv = ed.getMaxValue();
598        facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
599      }
600      if(ed.hasMaxLength()) {
601        int ml = ed.getMaxLength();
602        facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render());
603      }
604      if(ed.hasPattern()) {
605        Type pat = ed.getPattern();
606        facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render());
607      }
608      td_entry.add("facets", facets.toString());
609      return td_entry.render();
610
611    } else if (typ.getWorkingCode() == null) {
612      ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE);
613      primitive_entry.add("typ", "xsd:string");
614      return primitive_entry.render();
615
616    } else if(typ.getWorkingCode().equals("xhtml")) {
617      return tmplt(XHTML_TYPE_TEMPLATE).render();
618    } else {
619      datatypes.add(typ.getWorkingCode());
620      return simpleElement(sd, ed, typ.getWorkingCode());
621    }
622  }
623
624  /**
625   * Generate a set of alternative shapes
626   * @param ed Containing element definition
627   * @param id Element definition identifier
628   * @param shortId id to use in the actual definition
629   * @return ShEx list of alternative anonymous shapes separated by "OR"
630   */
631  private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) {
632    ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE);
633    List<String> altEntries = new ArrayList<String>();
634
635
636    for(ElementDefinition.TypeRefComponent typ : ed.getType())  {
637      altEntries.add(genAltEntry(id, typ));
638    }
639    shex_alt.add("altEntries", StringUtils.join(altEntries, " OR\n    "));
640    return shex_alt;
641  }
642
643
644
645  /**
646   * Generate an alternative shape for a reference
647   * @param id reference name
648   * @param typ shape type
649   * @return ShEx equivalent
650   */
651  private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) {
652    if(!typ.getWorkingCode().equals("Reference"))
653      throw new AssertionError("We do not handle " + typ.getWorkingCode() + " alternatives");
654
655    return genReference(id, typ);
656  }
657
658  /**
659   * Generate a list of type choices for a "name[x]" style id
660   * @param sd Structure containing ed
661   * @param ed element definition
662   * @param id choice identifier
663   * @return ShEx fragment for the set of choices
664   */
665  private String genChoiceTypes(StructureDefinition sd, ElementDefinition ed, String id) {
666    List<String> choiceEntries = new ArrayList<String>();
667    String base = id.replace("[x]", "");
668
669    for(ElementDefinition.TypeRefComponent typ : ed.getType())
670      choiceEntries.add(genChoiceEntry(sd, ed, id, base, typ));
671
672    return StringUtils.join(choiceEntries, " |\n");
673  }
674
675  /**
676   * Generate an entry in a choice list
677   * @param base base identifier
678   * @param typ type/discriminant
679   * @return ShEx fragment for choice entry
680   */
681  private String genChoiceEntry(StructureDefinition sd, ElementDefinition ed, String id, String base, ElementDefinition.TypeRefComponent typ) {
682    ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE);
683
684    String ext = typ.getWorkingCode();
685    shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " ");
686    shex_choice_entry.add("card", "");
687    shex_choice_entry.add("defn", genTypeRef(sd, ed, id, typ));
688    shex_choice_entry.add("comment", " ");
689    return shex_choice_entry.render();
690  }
691
692  /**
693   * Generate a definition for a referenced element
694   * @param sd Containing structure definition
695   * @param ed Inner element
696   * @return ShEx representation of element reference
697   */
698  private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) {
699    String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();;
700    ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE);
701    element_reference.add("resourceDecl", "");  // Not a resource
702    element_reference.add("id", path);
703    String comment = ed.getShort();
704    element_reference.add("comment", comment == null? " " : "# " + comment);
705
706    List<String> elements = new ArrayList<String>();
707    for (ElementDefinition child: ProfileUtilities.getChildList(sd, path, null))
708      elements.add(genElementDefinition(sd, child));
709
710    element_reference.add("elements", StringUtils.join(elements, "\n"));
711    return element_reference.render();
712  }
713
714  /**
715   * Generate a reference to a resource
716   * @param id attribute identifier
717   * @param typ possible reference types
718   * @return string that represents the result
719   */
720  private String genReference(String id, ElementDefinition.TypeRefComponent typ) {
721    ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE);
722
723    String ref = getTypeName(typ);
724    shex_ref.add("id", id);
725    shex_ref.add("ref", ref);
726    references.add(ref);
727    return shex_ref.render();
728  }
729
730  /**
731   * Return the type name for typ
732   * @param typ type to get name for
733   * @return name
734   */
735  private String getTypeName(ElementDefinition.TypeRefComponent typ) {
736    // TODO: This is brittle. There has to be a utility to do this...
737    if (typ.hasTargetProfile()) {
738      String[] els = typ.getTargetProfile().get(0).getValue().split("/");
739      return els[els.length - 1];
740    } else if (typ.hasProfile()) {
741      String[] els = typ.getProfile().get(0).getValue().split("/");
742      return els[els.length - 1];
743    } else {
744      return typ.getWorkingCode();
745    }
746  }
747
748  private String genValueSet(ValueSet vs) {
749    ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription());
750    ValueSetExpander.ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
751    List<String> valid_codes = new ArrayList<String>();
752    if(vse != null &&
753            vse.getValueset() != null &&
754            vse.getValueset().hasExpansion() &&
755            vse.getValueset().getExpansion().hasContains()) {
756      for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains())
757        valid_codes.add("\"" + vsec.getCode() + "\"");
758    }
759    return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " EXTERNAL").render();
760  }
761
762
763  // TODO: find a utility that implements this
764  private ValueSet resolveBindingReference(DomainResource ctxt, String reference) {
765    try {
766      return context.fetchResource(ValueSet.class, reference);
767    } catch (Throwable e) {
768      return null;
769    }
770  }
771}