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