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