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