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 hasType(String elementName) {
209    if (type != null) {
210      return false; // ?
211    } else if (definition.getType().size() == 0) {
212      return false;
213    } else if (isJsonPrimitiveChoice()) { 
214      for (TypeRefComponent tr : definition.getType()) {
215        if (elementName.equals(tr.getWorkingCode())) {
216          return true;
217        }
218      }
219      return false;
220    } else if (definition.getType().size() > 1) {
221      String t = definition.getType().get(0).getCode();
222      boolean all = true;
223      for (TypeRefComponent tr : definition.getType()) {
224        if (!t.equals(tr.getCode()))
225          all = false;
226      }
227      if (all)
228        return true;
229      String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
230      if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) {
231//        String name = elementName.substring(tail.length()-3);
232        return true;        
233      } else
234        return false;
235    } else
236      return true;
237  }
238
239        public StructureDefinition getStructure() {
240                return structure;
241        }
242
243        /**
244         * Is the given name a primitive
245         * 
246         * @param E.g. "Observation.status"
247         */
248        public boolean isPrimitiveName(String name) {
249          String code = getType(name);
250      return isPrimitive(code);
251        }
252
253        /**
254         * Is the given type a primitive
255         * 
256         * @param E.g. "integer"
257         */
258        public boolean isPrimitive(String code) {
259          return context.isPrimitiveType(code);
260        }
261
262        public boolean isPrimitive() {
263          return isPrimitive(getType());
264        }
265        private String lowFirst(String t) {
266                return t.substring(0, 1).toLowerCase()+t.substring(1);
267        }
268
269        public boolean isResource() {
270          if (type != null) {
271            String tc = type.getCode();
272      return (("Resource".equals(tc) || "DomainResource".equals(tc)) || utils.isResource(tc));
273          } else if (definition.getType().size() > 0) {
274      String tc = definition.getType().get(0).getCode();
275      return definition.getType().size() == 1 && (("Resource".equals(tc) || "DomainResource".equals(tc)) ||  utils.isResource(tc));
276    }
277          else {
278            return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE);
279          }
280        }
281
282  public boolean isList() {
283    return !"1".equals(definition.getBase().hasMax() ? definition.getBase().getMax() : definition.getMax());
284  }
285
286  public boolean isBaseList() {
287    return !"1".equals(definition.getBase().getMax());
288  }
289
290  public String getScopedPropertyName() {
291    return definition.getBase().getPath();
292  }
293
294  private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
295    boolean result = false;
296    if (!ed.getType().isEmpty()) {
297      result = true;
298      for (final ElementDefinition ele : children) {
299        if (!ele.getPath().contains("extension")) {
300          result = false;
301          break;
302        }
303      }
304    }
305    return result;
306  }
307  
308        public boolean IsLogicalAndHasPrimitiveValue(String name) {
309//              if (canBePrimitive!= null)
310//                      return canBePrimitive;
311                
312        if (structure.getKind() != StructureDefinitionKind.LOGICAL)
313                return false;
314        if (!hasType(name))
315                return false;
316        StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name));
317        if (sd == null)
318          sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), null));
319    if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
320      return true;
321        if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
322                return false;
323        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
324                if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) {
325                        return true;
326                }
327        }
328        return false;
329        }
330
331  public boolean isChoice() {
332    if (type != null) {
333      return true;
334    }
335    if (definition.getType().size() <= 1)
336      return false;
337    String tn = definition.getType().get(0).getCode();
338    for (int i = 1; i < definition.getType().size(); i++) 
339      if (!definition.getType().get(i).getCode().equals(tn))
340        return true;
341    return false;
342  }
343
344
345  public List<Property> getChildProperties(String elementName, String statedType) throws FHIRException {
346    String cacheKey = structure.getVUrl()+"#"+definition.getPath()+":"+elementName+"/"+statedType;
347    List<Property> cached = profileUtilities.getCachedPropertyList().get(cacheKey);
348    if (cached != null) {
349      return cached;
350    }
351    ElementDefinition ed = definition;
352    StructureDefinition sd = structure;
353    boolean isCDA = isCDAElement(structure);
354    SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed);
355    String url = null;
356    if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) {
357      // ok, find the right definitions
358      String t = null;
359      if (ed.getType().size() == 1 && (statedType == null || !isCDA))
360        t = ed.getType().get(0).getWorkingCode();
361      else if (ed.getType().size() == 0)
362        throw new Error("types == 0, and no children found on "+getDefinition().getPath());
363      else {
364        t = ed.getType().get(0).getWorkingCode();
365        boolean all = true;
366        for (TypeRefComponent tr : ed.getType()) {
367          if (!tr.getWorkingCode().equals(t)) {
368            all = false;
369            break;
370          }
371        }
372        if (!all || (isCDA && statedType != null)) {
373          // ok, it's polymorphic
374          if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR) || isCDA) {
375            t = statedType;
376            if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
377              t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
378            boolean ok = false;
379            for (TypeRefComponent tr : ed.getType()) { 
380              if (tr.getWorkingCode().equals(t)) 
381                ok = true;
382              if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) {
383                StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode());
384                if (sdt != null && sdt.getTypeTail().equals(t)) {
385                  url = tr.getWorkingCode();
386                  ok = true;
387                }
388                if (!ok) {
389                  sdt = findAncestor(t, sdt);
390                  if (sdt != null) {
391                    url = sdt.getUrl();
392                    ok = true;
393                  }
394                }
395              }
396              if (ok) {
397                break;
398              }
399            }
400            if (!ok) {
401              throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath());
402            }
403          } else {
404            t = elementName.substring(tail(ed.getPath()).length() - 3);
405            if (isPrimitive(lowFirst(t)))
406              t = lowFirst(t);
407          }
408        }
409      }
410      if (!"xhtml".equals(t)) {
411        for (TypeRefComponent aType: ed.getType()) {
412          if (aType.getWorkingCode().equals(t)) {
413            if (aType.hasProfile()) {
414              assert aType.getProfile().size() == 1; 
415              url = aType.getProfile().get(0).getValue();
416            } else {
417              url = ProfileUtilities.sdNs(t, null);
418            }
419            break;
420          }
421        }
422        if (url==null) {
423          throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath());
424        }
425        sd = context.fetchResource(StructureDefinition.class, url);        
426        if (sd == null)
427          throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
428        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
429      }
430    }
431    List<Property> properties = new ArrayList<Property>();
432    for (ElementDefinition child : children.getList()) {
433      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
434    }
435    profileUtilities.getCachedPropertyList().put(cacheKey, properties);
436    return properties;
437  }
438
439  private StructureDefinition findAncestor(String type, StructureDefinition sdt) {
440    if (sdt != null) {
441      StructureDefinition sd = context.fetchTypeDefinition(type);
442      StructureDefinition t = sd;
443      while (t != null) {
444        if (t == sdt) {
445          return sd; 
446        }
447        t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
448      }
449    }
450    return null;
451  }
452
453
454  private boolean isCDAElement(StructureDefinition sd) {
455    return sd.hasUrl() && sd.getUrl().startsWith(Constants.NS_CDA_ROOT);
456  }
457
458
459  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
460    ElementDefinition ed = definition;
461    StructureDefinition sd = structure;
462    SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed);
463    if (children.getList().isEmpty()) {
464      // ok, find the right definitions
465      String t = null;
466      if (ed.getType().size() == 1)
467        t = ed.getType().get(0).getCode();
468      else if (ed.getType().size() == 0)
469        throw new Error("types == 0, and no children found");
470      else {
471        t = ed.getType().get(0).getCode();
472        boolean all = true;
473        for (TypeRefComponent tr : ed.getType()) {
474          if (!tr.getCode().equals(t)) {
475            all = false;
476            break;
477          }
478        }
479        if (!all) {
480          // ok, it's polymorphic
481          t = type.getType();
482        }
483      }
484      if (!"xhtml".equals(t)) {
485        sd = context.fetchResource(StructureDefinition.class, t);
486        if (sd == null)
487          throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
488        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
489      }
490    }
491    List<Property> properties = new ArrayList<Property>();
492    for (ElementDefinition child : children.getList()) {
493      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
494    }
495    return properties;
496  }
497
498  private String tail(String path) {
499    return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
500  }
501
502  public Property getChild(String elementName, String childName) throws FHIRException {
503    List<Property> children = getChildProperties(elementName, null);
504    for (Property p : children) {
505      if (p.getName().equals(childName)) {
506        return p;
507      }
508    }
509    return null;
510  }
511
512  public Property getChild(String name, TypeDetails type) throws DefinitionException {
513    List<Property> children = getChildProperties(type);
514    for (Property p : children) {
515      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
516        return p;
517      }
518    }
519    return null;
520  }
521
522  public Property getChild(String name) throws FHIRException {
523    List<Property> children = getChildProperties(name, null);
524    for (Property p : children) {
525      if (p.getName().equals(name)) {
526        return p;
527      }
528    }
529    return null;
530  }
531
532  public Property getChildSimpleName(String elementName, String name) throws FHIRException {
533    List<Property> children = getChildProperties(elementName, null);
534    for (Property p : children) {
535      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
536        return p;
537      }
538    }
539    return null;
540  }
541
542  public IWorkerContext getContext() {
543    return context;
544  }
545
546  @Override
547  public String toString() {
548    return definition.getPath();
549  }
550
551
552  public boolean isJsonKeyArray() {
553    return definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY);
554  }
555
556
557  public String getJsonKeyProperty() {
558    return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_PROP_KEY);
559  }
560
561
562  public boolean hasTypeSpecifier() {
563    return definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC);
564  }
565
566
567  public List<StringPair> getTypeSpecifiers() {
568    List<StringPair> res = new ArrayList<>();
569    for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
570      res.add(new StringPair(ToolingExtensions.readStringExtension(e,  "condition"), ToolingExtensions.readStringExtension(e,  "type")));
571    }
572    return res;
573  }
574
575
576  public Property cloneToType(StructureDefinition sd) {
577    Property res = new Property(context, definition.copy(), sd);
578    res.definition.getType().clear();
579    res.definition.getType().add(new TypeRefComponent(sd.getUrl()));
580    return res;
581  }
582
583
584  public boolean hasImpliedPrefix() {
585    return definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX);
586  }
587
588
589  public String getImpliedPrefix() {
590    return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX);
591  }
592
593
594  public boolean isNullable() {    
595    return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE);
596  }
597
598
599  public String summary() {
600    return structure.getUrl()+"#"+definition.getId();
601  }
602
603
604  public boolean canBeEmpty() {
605    if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
606      return !"absent".equals(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY));
607    } else {
608      return false;
609    }
610  }
611
612
613  public boolean isLogical() {
614    return structure.getKind() == StructureDefinitionKind.LOGICAL;
615  }
616
617
618  public ProfileUtilities getUtils() {
619    return profileUtilities;
620  }
621  public ContextUtilities getContextUtils() {
622    return utils;
623  }
624
625  public boolean isJsonPrimitiveChoice() {
626    return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE);
627  }
628
629  public Object typeSummary() {
630    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" | ");
631    for (TypeRefComponent t : definition.getType()) {
632      b.append(t.getCode());
633    }
634    return b.toString();
635  }
636
637
638  public boolean hasJsonName() {
639    return definition.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED);
640  }
641
642
643  public boolean isTranslatable() {
644    boolean ok = ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_TRANSLATABLE);
645    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")) {
646      String t = getType();
647      ok = Utilities.existsInList(t, "string", "markdown");
648    }
649    if (Utilities.existsInList(pathForElement(getStructure().getType(), getDefinition().getBase().getPath()), "CanonicalResource.version")) {
650      return false;
651    }
652    return ok;
653  }  
654
655
656  private String pathForElement(String type, String path) {
657    // special case support for metadata elements prior to R5:
658    if (utils.getCanonicalResourceNames().contains(type)) {
659      String fp = path.replace(type+".", "CanonicalResource.");
660      if (Utilities.existsInList(fp,
661         "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 
662         "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date",
663         "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 
664         "CanonicalResource.jurisdiction"))  {
665        return fp;
666      }
667    }
668    return path; 
669  }
670  
671  public String getXmlTypeName() {
672    TypeRefComponent tr = type;
673    if (tr == null) {
674      tr = definition.getTypeFirstRep();
675    }
676    StructureDefinition sd = context.fetchTypeDefinition(tr.getWorkingCode());
677    return sd.getSnapshot().getElementFirstRep().getPath();
678  }
679
680
681  public boolean isReference() {
682    if (type != null) {
683      return isRef(type);
684    }
685    for (TypeRefComponent tr : definition.getType()) {
686      boolean ref = isRef(tr);
687      if (ref) {
688        return true;
689      }
690    }
691    return false;
692  }
693
694
695  private boolean isRef(TypeRefComponent tr) {
696    return Utilities.existsInList(tr.getWorkingCode(), "Reference", "url", "uri", "canonical");
697  }
698
699
700  public boolean canBeType(String type) {
701    for (TypeRefComponent tr : getDefinition().getType()) {
702      if (type.equals(tr.getWorkingCode())) {
703        return true;
704      }
705    }
706    return false;
707  }
708
709  
710}