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.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        sd = context.fetchResource(StructureDefinition.class, url);        
425        if (sd == null)
426          throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
427        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
428      }
429    }
430    List<Property> properties = new ArrayList<Property>();
431    for (ElementDefinition child : children.getList()) {
432      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
433    }
434    profileUtilities.getCachedPropertyList().put(cacheKey, properties);
435    return properties;
436  }
437
438  private StructureDefinition findAncestor(String type, StructureDefinition sdt) {
439    if (sdt != null) {
440      StructureDefinition sd = context.fetchTypeDefinition(type);
441      StructureDefinition t = sd;
442      while (t != null) {
443        if (t == sdt) {
444          return sd; 
445        }
446        t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
447      }
448    }
449    return null;
450  }
451
452
453  private boolean isCDAElement(StructureDefinition sd) {
454    return sd.hasUrl() && sd.getUrl().startsWith(Constants.NS_CDA_ROOT);
455  }
456
457
458  protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
459    ElementDefinition ed = definition;
460    StructureDefinition sd = structure;
461    SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed);
462    if (children.getList().isEmpty()) {
463      // ok, find the right definitions
464      String t = null;
465      if (ed.getType().size() == 1)
466        t = ed.getType().get(0).getCode();
467      else if (ed.getType().size() == 0)
468        throw new Error("types == 0, and no children found");
469      else {
470        t = ed.getType().get(0).getCode();
471        boolean all = true;
472        for (TypeRefComponent tr : ed.getType()) {
473          if (!tr.getCode().equals(t)) {
474            all = false;
475            break;
476          }
477        }
478        if (!all) {
479          // ok, it's polymorphic
480          t = type.getType();
481        }
482      }
483      if (!"xhtml".equals(t)) {
484        sd = context.fetchResource(StructureDefinition.class, t);
485        if (sd == null)
486          throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
487        children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
488      }
489    }
490    List<Property> properties = new ArrayList<Property>();
491    for (ElementDefinition child : children.getList()) {
492      properties.add(new Property(context, child, sd, this.profileUtilities, this.utils));
493    }
494    return properties;
495  }
496
497  private String tail(String path) {
498    return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
499  }
500
501  public Property getChild(String elementName, String childName) throws FHIRException {
502    List<Property> children = getChildProperties(elementName, null);
503    for (Property p : children) {
504      if (p.getName().equals(childName)) {
505        return p;
506      }
507    }
508    return null;
509  }
510
511  public Property getChild(String name, TypeDetails type) throws DefinitionException {
512    List<Property> children = getChildProperties(type);
513    for (Property p : children) {
514      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
515        return p;
516      }
517    }
518    return null;
519  }
520
521  public Property getChild(String name) throws FHIRException {
522    List<Property> children = getChildProperties(name, null);
523    for (Property p : children) {
524      if (p.getName().equals(name)) {
525        return p;
526      }
527    }
528    return null;
529  }
530
531  public Property getChildSimpleName(String elementName, String name) throws FHIRException {
532    List<Property> children = getChildProperties(elementName, null);
533    for (Property p : children) {
534      if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
535        return p;
536      }
537    }
538    return null;
539  }
540
541  public IWorkerContext getContext() {
542    return context;
543  }
544
545  @Override
546  public String toString() {
547    return definition.getPath();
548  }
549
550
551  public boolean isJsonKeyArray() {
552    return definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY);
553  }
554
555
556  public String getJsonKeyProperty() {
557    return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_PROP_KEY);
558  }
559
560
561  public boolean hasTypeSpecifier() {
562    return definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC);
563  }
564
565
566  public List<StringPair> getTypeSpecifiers() {
567    List<StringPair> res = new ArrayList<>();
568    for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
569      res.add(new StringPair(ToolingExtensions.readStringExtension(e,  "condition"), ToolingExtensions.readStringExtension(e,  "type")));
570    }
571    return res;
572  }
573
574
575  public Property cloneToType(StructureDefinition sd) {
576    Property res = new Property(context, definition.copy(), sd);
577    res.definition.getType().clear();
578    res.definition.getType().add(new TypeRefComponent(sd.getUrl()));
579    return res;
580  }
581
582
583  public boolean hasImpliedPrefix() {
584    return definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX);
585  }
586
587
588  public String getImpliedPrefix() {
589    return ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX);
590  }
591
592
593  public boolean isNullable() {    
594    return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE);
595  }
596
597
598  public String summary() {
599    return structure.getUrl()+"#"+definition.getId();
600  }
601
602
603  public boolean canBeEmpty() {
604    if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
605      return !"absent".equals(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY));
606    } else {
607      return false;
608    }
609  }
610
611
612  public boolean isLogical() {
613    return structure.getKind() == StructureDefinitionKind.LOGICAL;
614  }
615
616
617  public ProfileUtilities getUtils() {
618    return profileUtilities;
619  }
620  public ContextUtilities getContextUtils() {
621    return utils;
622  }
623
624  public boolean isJsonPrimitiveChoice() {
625    return ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE);
626  }
627
628  public Object typeSummary() {
629    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" | ");
630    for (TypeRefComponent t : definition.getType()) {
631      b.append(t.getCode());
632    }
633    return b.toString();
634  }
635
636
637  public boolean hasJsonName() {
638    return definition.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED);
639  }
640
641
642  public boolean isTranslatable() {
643    boolean ok = ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_TRANSLATABLE);
644    if (!ok && !definition.getPath().endsWith(".id") && !Utilities.existsInList(definition.getBase().getPath(), "Resource.id", "Reference.reference", "Coding.version", "Identifier.value", "SampledData.offsets", "SampledData.data", "ContactPoint.value")) {
645      String t = getType();
646      ok = Utilities.existsInList(t, "string", "markdown");
647    }
648    if (Utilities.existsInList(pathForElement(getStructure().getType(), getDefinition().getBase().getPath()), "CanonicalResource.version")) {
649      return false;
650    }
651    return ok;
652  }  
653
654
655  private String pathForElement(String type, String path) {
656    // special case support for metadata elements prior to R5:
657    if (utils.getCanonicalResourceNames().contains(type)) {
658      String fp = path.replace(type+".", "CanonicalResource.");
659      if (Utilities.existsInList(fp,
660         "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 
661         "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date",
662         "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 
663         "CanonicalResource.jurisdiction"))  {
664        return fp;
665      }
666    }
667    return path; 
668  }
669  
670  public String getXmlTypeName() {
671    TypeRefComponent tr = type;
672    if (tr == null) {
673      tr = definition.getTypeFirstRep();
674    }
675    StructureDefinition sd = context.fetchTypeDefinition(tr.getWorkingCode());
676    return sd.getSnapshot().getElementFirstRep().getPath();
677  }
678
679
680  public boolean isReference() {
681    if (type != null) {
682      return isRef(type);
683    }
684    for (TypeRefComponent tr : definition.getType()) {
685      boolean ref = isRef(tr);
686      if (ref) {
687        return true;
688      }
689    }
690    return false;
691  }
692
693
694  private boolean isRef(TypeRefComponent tr) {
695    return Utilities.existsInList(tr.getWorkingCode(), "Reference", "url", "uri", "canonical");
696  }
697
698
699  public boolean canBeType(String type) {
700    for (TypeRefComponent tr : getDefinition().getType()) {
701      if (type.equals(tr.getWorkingCode())) {
702        return true;
703      }
704    }
705    return false;
706  }
707
708  
709}