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