001package org.hl7.fhir.r5.elementmodel;
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.List;
036
037import org.hl7.fhir.exceptions.DefinitionException;
038import org.hl7.fhir.exceptions.FHIRException;
039import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
040import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions;
041import org.hl7.fhir.r5.context.ContextUtilities;
042import org.hl7.fhir.r5.context.IWorkerContext;
043import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
044import org.hl7.fhir.r5.extensions.ExtensionUtilities;
045import org.hl7.fhir.r5.fhirpath.TypeDetails;
046import org.hl7.fhir.r5.formats.FormatUtilities;
047import org.hl7.fhir.r5.model.Constants;
048import org.hl7.fhir.r5.model.ElementDefinition;
049import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
050import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
051import org.hl7.fhir.r5.model.Extension;
052import org.hl7.fhir.r5.model.StructureDefinition;
053import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
054
055import org.hl7.fhir.r5.utils.TypesUtilities;
056import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
057import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
058import org.hl7.fhir.utilities.StringPair;
059import org.hl7.fhir.utilities.Utilities;
060
061@MarkedToMoveToAdjunctPackage
062public class Property {
063
064        private IWorkerContext context;
065        private ElementDefinition definition;
066        private StructureDefinition structure;
067  private ProfileUtilities profileUtilities;
068  private ContextUtilities utils;
069  private TypeRefComponent type;
070
071  public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities, ContextUtilities utils) {
072                this.context = context;
073                this.definition = definition;
074                this.structure = structure;
075                this.utils = utils;
076    this.profileUtilities = profileUtilities;
077        }
078
079
080  public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities, ContextUtilities utils, String type) {
081    this.context = context;
082    this.definition = definition;
083    this.structure = structure;
084    this.profileUtilities = profileUtilities;
085    this.utils = utils;
086    for (TypeRefComponent tr : definition.getType()) {
087      if (tr.getWorkingCode().equals(type)) {
088        this.type = tr;
089      }
090    }
091  }
092  
093        public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) {
094    this(context, definition, structure, new ProfileUtilities(context, null, null), new ContextUtilities(context));
095        }
096
097        public String getName() {
098                return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
099        }
100
101  public String getJsonName() {
102    if (definition.hasExtension(ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED)) {
103      return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED);
104    } else {
105      return getName();
106    }
107  }
108
109  public String getXmlName() {
110    if (definition.hasExtension(ExtensionDefinitions.EXT_XML_NAME)) {
111      return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_XML_NAME);
112    } else if (definition.hasExtension(ExtensionDefinitions.EXT_XML_NAME_DEPRECATED)) {
113      return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_XML_NAME_DEPRECATED);
114    } else {
115      return getName();
116    }
117  }
118
119  public String getXmlNamespace() {
120    if (ExtensionUtilities.hasAnyOfExtensions(definition, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED)) {
121      return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED);
122    } else if (ExtensionUtilities.hasAnyOfExtensions(structure, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED)) {
123      return ExtensionUtilities.readStringExtension(structure, ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED);
124    } else {
125      return FormatUtilities.FHIR_NS;
126    }
127  }
128        
129        public ElementDefinition getDefinition() {
130                return definition;
131        }
132
133        public String getType() {
134          if (type != null) {
135            return type.getWorkingCode();
136          } else  if (definition.getType().size() == 0)
137                        return null;
138                else if (definition.getType().size() > 1) {
139                        String tn = definition.getType().get(0).getWorkingCode();
140                        for (int i = 1; i < definition.getType().size(); i++) {
141                                if (!tn.equals(definition.getType().get(i).getWorkingCode()))
142                                        return null; // though really, we shouldn't get here - type != null when definition.getType.size() > 1, or it should be
143                        }
144                        return tn;
145                } else
146                        return definition.getType().get(0).getWorkingCode();
147        }
148
149        public String getType(String elementName) {
150          if (type != null) {
151      return type.getWorkingCode();
152    } 
153          if (!definition.getPath().contains("."))
154      return definition.getPath();
155    ElementDefinition ed = definition;
156    if (definition.hasContentReference()) {
157      String url = null;
158      String path = definition.getContentReference();
159      if (!path.startsWith("#")) {
160        if (path.contains("#")) {
161          url = path.substring(0, path.indexOf("#"));
162          path = path.substring(path.indexOf("#")+1);
163        } else {
164          throw new Error("Illegal content reference '"+path+"'");
165        }
166      } else {
167        path = path.substring(1);
168      }
169      StructureDefinition sd = (url == null || url.equals(structure.getUrl())) ? structure : context.fetchResource(StructureDefinition.class, url, structure);
170      if (sd == null) {
171        throw new Error("Unknown Type in content reference '"+path+"'");        
172      }
173      boolean found = false;
174      for (ElementDefinition d : sd.getSnapshot().getElement()) {
175        if (d.hasId() && d.getId().equals(path)) {
176          found = true;
177          ed = d;
178        }
179      }
180      if (!found)
181        throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+sd.getUrl());
182    }
183    if (ed.getType().size() == 0)
184                        return null;
185    else if (ed.getType().size() > 1) {
186      String t = ed.getType().get(0).getCode();
187                        boolean all = true;
188      for (TypeRefComponent tr : ed.getType()) {
189                                if (!t.equals(tr.getCode()))
190                                        all = false;
191                        }
192                        if (all)
193                                return t;
194      String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1);
195      if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) {
196                                String name = elementName.substring(tail.length()-3);
197        return isPrimitive(lowFirst(name)) ? lowFirst(name) : name;        
198                        } else {
199              if (ExtensionUtilities.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
200                return ExtensionUtilities.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
201        throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath());
202                        }
203    } else if (ed.getType().get(0).getCode() == null) {
204      if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url"))
205        return "string";
206      else
207        return structure.getId();
208                } else
209      return ed.getType().get(0).getWorkingCode();
210        }
211
212  public boolean typeIsConsistent(String typeName) {
213    for (TypeRefComponent tr : definition.getType()) {
214      if (typeName.equals(tr.getWorkingCode()) || typeSpecializes(tr.getWorkingCode(), typeName)) {
215        return true;
216      }
217    }
218    return false;
219  }
220
221  
222  private boolean typeSpecializes(String workingCode, String typeName) {
223    if ("string".equals(typeName)) {
224      return Utilities.existsInList(workingCode, "uri", "oid", "canonical", "url", "uuid", "id", "markdown");
225    }
226    if ("integer".equals(typeName)) {
227      return Utilities.existsInList(workingCode, "positiveInt", "unsignedInt");
228    }
229    return false;
230  }
231
232
233  public boolean hasType(String typeName) {
234    if (type != null) {
235      return false; // ?
236    } else if (definition.getType().size() == 0) {
237      return false;
238    } else if (isJsonPrimitiveChoice()) { 
239      for (TypeRefComponent tr : definition.getType()) {
240        if (typeName.equals(tr.getWorkingCode())) {
241          return true;
242        }
243      }
244      return false;
245    } else if (definition.getType().size() > 1) {
246      String t = definition.getType().get(0).getCode();
247      boolean all = true;
248      for (TypeRefComponent tr : definition.getType()) {
249        if (!t.equals(tr.getCode()))
250          all = false;
251      }
252      if (all)
253        return true;
254      String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
255      if (tail.endsWith("[x]") && typeName.startsWith(tail.substring(0, tail.length()-3))) {
256//        String name = elementName.substring(tail.length()-3);
257        return true;        
258      } else
259        return false;
260    } else
261      return true;
262  }
263
264        public StructureDefinition getStructure() {
265                return structure;
266        }
267
268        /**
269         * Is the given name a primitive
270         * 
271         * @param E.g. "Observation.status"
272         */
273        public boolean isPrimitiveName(String name) {
274          String code = getType(name);
275      return isPrimitive(code);
276        }
277
278        /**
279         * Is the given type a primitive
280         * 
281         * @param E.g. "integer"
282         */
283        public boolean isPrimitive(String code) {
284          return context.isPrimitiveType(code);
285        }
286
287        public boolean isPrimitive() {
288          return isPrimitive(getType());
289        }
290        private String lowFirst(String t) {
291                return t.substring(0, 1).toLowerCase()+t.substring(1);
292        }
293
294        public boolean isResource() {
295          if (type != null) {
296            String tc = type.getCode();
297      return (("Resource".equals(tc) || "DomainResource".equals(tc)) || utils.isResource(tc));
298          } else if (definition.getType().size() > 0) {
299      String tc = definition.getType().get(0).getCode();
300      return definition.getType().size() == 1 && (("Resource".equals(tc) || "DomainResource".equals(tc)) ||  utils.isResource(tc));
301    }
302          else {
303            return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE);
304          }
305        }
306
307  public boolean isList() {
308    return !"1".equals(definition.getBase().hasMax() ? definition.getBase().getMax() : definition.getMax());
309  }
310
311  /**
312   * This handles a very special case: An extension used with json extensions in CDS hooks, 
313   * where the extension definition, not the base, decides whether it's an array or not 
314   * @return
315   */
316  public boolean isJsonList() {
317    if (definition.hasExtension(ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED)) {
318      return !"1".equals(definition.getMax());
319    } else {
320      return !"1".equals(definition.getBase().hasMax() ? definition.getBase().getMax() : definition.getMax());
321    }
322  }
323
324  public boolean isBaseList() {
325    return !"1".equals(definition.getBase().getMax());
326  }
327
328  public String getScopedPropertyName() {
329    return definition.getBase().getPath();
330  }
331
332  private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
333    boolean result = false;
334    if (!ed.getType().isEmpty()) {
335      result = true;
336      for (final ElementDefinition ele : children) {
337        if (!ele.getPath().contains("extension")) {
338          result = false;
339          break;
340        }
341      }
342    }
343    return result;
344  }
345  
346        public boolean IsLogicalAndHasPrimitiveValue(String name) {
347//              if (canBePrimitive!= null)
348//                      return canBePrimitive;
349                
350        if (structure.getKind() != StructureDefinitionKind.LOGICAL)
351                return false;
352        if (!hasType(name))
353                return false;
354        StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name));
355        if (sd == null)
356          sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), null));
357    if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
358      return true;
359        if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
360                return false;
361        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
362                if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) {
363                        return true;
364                }
365        }
366        return false;
367        }
368
369  public boolean isChoice() {
370    if (type != null) {
371      return true;
372    }
373    if (definition.getType().size() <= 1)
374      return false;
375    String tn = definition.getType().get(0).getCode();
376    for (int i = 1; i < definition.getType().size(); i++) 
377      if (!definition.getType().get(i).getCode().equals(tn))
378        return true;
379    return false;
380  }
381
382
383  public List<Property> getChildProperties(String elementName, String statedType) throws FHIRException {
384    String cacheKey = structure.getVUrl()+"#"+definition.getPath()+":"+elementName+"/"+statedType;
385    List<Property> cached = profileUtilities.getCachedPropertyList().get(cacheKey);
386    if (cached != null) {
387      return cached;
388    }
389    ElementDefinition ed = definition;
390    StructureDefinition sd = structure;
391    boolean isCDA = isCDAElement(structure);
392    SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false);
393    String url = null;
394    if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) {
395      // ok, find the right definitions
396      String t = null;
397      if (ed.getType().size() == 1 && (statedType == null || !isCDA))
398        t = ed.getType().get(0).getWorkingCode();
399      else if (ed.getType().size() == 0)
400        throw new Error("types == 0, and no children found on "+getDefinition().getPath());
401      else {
402        t = ed.getType().get(0).getWorkingCode();
403        boolean all = true;
404        for (TypeRefComponent tr : ed.getType()) {
405          if (!tr.getWorkingCode().equals(t)) {
406            all = false;
407            break;
408          }
409        }
410        if (!all || (isCDA && statedType != null)) {
411          // ok, it's polymorphic
412          if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR) || isCDA) {
413            t = statedType;
414            if (t == null && ExtensionUtilities.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
415              t = ExtensionUtilities.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
416            boolean ok = false;
417            for (TypeRefComponent tr : ed.getType()) { 
418              if (tr.getWorkingCode().equals(t)) 
419                ok = true;
420              if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) {
421                StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode());
422                if (sdt != null && sdt.getTypeTail().equals(t)) {
423                  url = tr.getWorkingCode();
424                  ok = true;
425                }
426                if (!ok) {
427                  sdt = findAncestor(t, sdt);
428                  if (sdt != null) {
429                    url = sdt.getUrl();
430                    ok = true;
431                  }
432                }
433              }
434              if (ok) {
435                break;
436              }
437            }
438            if (!ok) {
439              throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath());
440            }
441          } else {
442            t = elementName.substring(tail(ed.getPath()).length() - 3);
443            if (isPrimitive(lowFirst(t)))
444              t = lowFirst(t);
445          }
446        }
447      }
448      if (!"xhtml".equals(t)) {
449        for (TypeRefComponent aType: ed.getType()) {
450          if (aType.getWorkingCode().equals(t)) {
451            if (aType.hasProfile()) {
452              assert aType.getProfile().size() == 1; 
453              url = aType.getProfile().get(0).getValue();
454            } else {
455              url = ProfileUtilities.sdNs(t, null);
456            }
457            break;
458          }
459        }
460        if (url==null) {
461          throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath());
462        }
463        sd = context.fetchResource(StructureDefinition.class, url);        
464        if (sd == null)
465          throw new DefinitionException("Unable to find definition '"+url+"' for type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
466        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false);
467      }
468    }
469    List<Property> properties = new ArrayList<Property>();
470    for (ElementDefinition child : children.getList()) {
471      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
472    }
473    profileUtilities.getCachedPropertyList().put(cacheKey, properties);
474    return properties;
475  }
476
477  private StructureDefinition findAncestor(String type, StructureDefinition sdt) {
478    if (sdt != null) {
479      StructureDefinition sd = context.fetchTypeDefinition(type);
480      StructureDefinition t = sd;
481      while (t != null) {
482        if (t == sdt) {
483          return sd; 
484        }
485        t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
486      }
487    }
488    return null;
489  }
490
491
492  private boolean isCDAElement(StructureDefinition sd) {
493    return sd.hasUrl() && sd.getUrl().startsWith(Constants.NS_CDA_ROOT);
494  }
495
496
497  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
498    ElementDefinition ed = definition;
499    StructureDefinition sd = structure;
500    SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false);
501    if (children.getList().isEmpty()) {
502      // ok, find the right definitions
503      String t = null;
504      if (ed.getType().size() == 1)
505        t = ed.getType().get(0).getCode();
506      else if (ed.getType().size() == 0)
507        throw new Error("types == 0, and no children found");
508      else {
509        t = ed.getType().get(0).getCode();
510        boolean all = true;
511        for (TypeRefComponent tr : ed.getType()) {
512          if (!tr.getCode().equals(t)) {
513            all = false;
514            break;
515          }
516        }
517        if (!all) {
518          // ok, it's polymorphic
519          t = type.getType();
520        }
521      }
522      if (!"xhtml".equals(t)) {
523        sd = context.fetchResource(StructureDefinition.class, t);
524        if (sd == null)
525          throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
526        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false);
527      }
528    }
529    List<Property> properties = new ArrayList<Property>();
530    for (ElementDefinition child : children.getList()) {
531      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
532    }
533    return properties;
534  }
535
536  private String tail(String path) {
537    return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
538  }
539
540  public Property getChild(String elementName, String childName) throws FHIRException {
541    List<Property> children = getChildProperties(elementName, null);
542    for (Property p : children) {
543      if (p.getName().equals(childName)) {
544        return p;
545      }
546    }
547    return null;
548  }
549
550  public Property getChild(String name, TypeDetails type) throws DefinitionException {
551    List<Property> children = getChildProperties(type);
552    for (Property p : children) {
553      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
554        return p;
555      }
556    }
557    return null;
558  }
559
560  public Property getChild(String name) throws FHIRException {
561    List<Property> children = getChildProperties(name, null);
562    for (Property p : children) {
563      if (p.getName().equals(name)) {
564        return p;
565      }
566    }
567    return null;
568  }
569
570  public Property getChildSimpleName(String elementName, String name) throws FHIRException {
571    List<Property> children = getChildProperties(elementName, null);
572    for (Property p : children) {
573      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
574        return p;
575      }
576    }
577    return null;
578  }
579
580  public IWorkerContext getContext() {
581    return context;
582  }
583
584  @Override
585  public String toString() {
586    return definition.getPath();
587  }
588
589
590  public boolean isJsonKeyArray() {
591    return definition.hasExtension(ExtensionDefinitions.EXT_JSON_PROP_KEY);
592  }
593
594
595  public String getJsonKeyProperty() {
596    return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_JSON_PROP_KEY);
597  }
598
599
600  public boolean hasTypeSpecifier() {
601    return definition.hasExtension(ExtensionDefinitions.EXT_TYPE_SPEC);
602  }
603
604
605  public List<StringPair> getTypeSpecifiers() {
606    List<StringPair> res = new ArrayList<>();
607    for (Extension e : definition.getExtensionsByUrl(ExtensionDefinitions.EXT_TYPE_SPEC)) {
608      res.add(new StringPair(ExtensionUtilities.readStringExtension(e,  "condition"), ExtensionUtilities.readStringExtension(e,  "type")));
609    }
610    return res;
611  }
612
613
614  public Property cloneToType(StructureDefinition sd) {
615    Property res = new Property(context, definition.copy(), sd);
616    res.definition.getType().clear();
617    res.definition.getType().add(new TypeRefComponent(sd.getUrl()));
618    return res;
619  }
620
621
622  public boolean hasImpliedPrefix() {
623    return definition.hasExtension(ExtensionDefinitions.EXT_IMPLIED_PREFIX);
624  }
625
626
627  public String getImpliedPrefix() {
628    return ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_IMPLIED_PREFIX);
629  }
630
631
632  public boolean isNullable() {    
633    return ExtensionUtilities.readBoolExtension(definition, ExtensionDefinitions.EXT_JSON_NULLABLE);
634  }
635
636
637  public String summary() {
638    return structure.getUrl()+"#"+definition.getId();
639  }
640
641
642  public boolean canBeEmpty() {
643    if (definition.hasExtension(ExtensionDefinitions.EXT_JSON_EMPTY)) {
644      return !"absent".equals(ExtensionUtilities.readStringExtension(definition, ExtensionDefinitions.EXT_JSON_EMPTY));
645    } else {
646      return false;
647    }
648  }
649
650
651  public boolean isLogical() {
652    return structure.getKind() == StructureDefinitionKind.LOGICAL;
653  }
654
655
656  public ProfileUtilities getUtils() {
657    return profileUtilities;
658  }
659  public ContextUtilities getContextUtils() {
660    return utils;
661  }
662
663  public boolean isJsonPrimitiveChoice() {
664    return ExtensionUtilities.readBoolExtension(definition, ExtensionDefinitions.EXT_JSON_PRIMITIVE_CHOICE);
665  }
666
667  public Object typeSummary() {
668    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" | ");
669    for (TypeRefComponent t : definition.getType()) {
670      b.append(t.getCode());
671    }
672    return b.toString();
673  }
674
675
676  public boolean hasJsonName() {
677    return definition.hasExtension(ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED);
678  }
679
680
681  public boolean isTranslatable() {
682    boolean ok = ExtensionUtilities.readBoolExtension(definition, ExtensionDefinitions.EXT_TRANSLATABLE);
683    if (!ok && !definition.getPath().endsWith(".id") && !definition.getPath().endsWith(".linkId") && !Utilities.existsInList(definition.getBase().getPath(), "Resource.id", "Reference.reference", "Coding.version", "Identifier.value", "SampledData.offsets", "SampledData.data", "ContactPoint.value")) {
684      String t = getType();
685      ok = Utilities.existsInList(t, "string", "markdown");
686    }
687    if (Utilities.existsInList(pathForElement(getStructure().getType(), getDefinition().getBase().getPath()), "CanonicalResource.version")) {
688      return false;
689    }
690    return ok;
691  }  
692
693
694  private String pathForElement(String type, String path) {
695    // special case support for metadata elements prior to R5:
696    if (utils.getCanonicalResourceNames().contains(type)) {
697      String fp = path.replace(type+".", "CanonicalResource.");
698      if (Utilities.existsInList(fp,
699         "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 
700         "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date",
701         "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 
702         "CanonicalResource.jurisdiction"))  {
703        return fp;
704      }
705    }
706    return path; 
707  }
708  
709  public String getXmlTypeName() {
710    TypeRefComponent tr = type;
711    if (tr == null) {
712      tr = definition.getTypeFirstRep();
713    }
714    StructureDefinition sd = context.fetchTypeDefinition(tr.getWorkingCode());
715    return sd.getSnapshot().getElementFirstRep().getPath();
716  }
717
718
719  public boolean isReference() {
720    if (type != null) {
721      return isRef(type);
722    }
723    for (TypeRefComponent tr : definition.getType()) {
724      boolean ref = isRef(tr);
725      if (ref) {
726        return true;
727      }
728    }
729    return false;
730  }
731
732
733  private boolean isRef(TypeRefComponent tr) {
734    return Utilities.existsInList(tr.getWorkingCode(), "Reference", "url", "uri", "canonical");
735  }
736
737
738  public boolean canBeType(String type) {
739    for (TypeRefComponent tr : getDefinition().getType()) {
740      if (type.equals(tr.getWorkingCode())) {
741        return true;
742      }
743    }
744    return false;
745  }
746
747  public String getExtensionStyle() {
748    ElementDefinition ed = getDefinition();
749    if (ed.hasExtension(ExtensionDefinitions.EXT_EXTENSION_STYLE_NEW, ExtensionDefinitions.EXT_EXTENSION_STYLE_DEPRECATED)) {
750      return ed.getExtensionString(ExtensionDefinitions.EXT_EXTENSION_STYLE_NEW, ExtensionDefinitions.EXT_EXTENSION_STYLE_DEPRECATED);
751    }
752    if (ed.getType().size() == 1) {
753      StructureDefinition sd = context.fetchTypeDefinition(ed.getTypeFirstRep().getWorkingCode());
754      if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_EXTENSION_STYLE_NEW, ExtensionDefinitions.EXT_EXTENSION_STYLE_DEPRECATED)) {
755        return sd.getExtensionString(ExtensionDefinitions.EXT_EXTENSION_STYLE_NEW, ExtensionDefinitions.EXT_EXTENSION_STYLE_DEPRECATED);
756      }
757    }
758    return null;
759  }
760}