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