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