001package org.hl7.fhir.r4.elementmodel;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031
032import org.apache.commons.lang3.Validate;
033import org.hl7.fhir.exceptions.FHIRException;
034import org.hl7.fhir.r4.conformance.ProfileUtilities;
035import org.hl7.fhir.r4.model.Base;
036import org.hl7.fhir.r4.model.ElementDefinition;
037import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
038import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
039import org.hl7.fhir.r4.model.ICoding;
040import org.hl7.fhir.r4.model.StringType;
041import org.hl7.fhir.r4.model.StructureDefinition;
042import org.hl7.fhir.r4.model.Type;
043import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
044import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
045import org.hl7.fhir.utilities.ElementDecoration;
046import org.hl7.fhir.utilities.ElementDecoration.DecorationType;
047import org.hl7.fhir.utilities.Utilities;
048import org.hl7.fhir.utilities.xhtml.XhtmlNode;
049
050/**
051 * This class represents the underlying reference model of FHIR
052 * 
053 * A resource is nothing but a set of elements, where every element has a 
054 * name, maybe a stated type, maybe an id, and either a value or child elements 
055 * (one or the other, but not both or neither)
056 * 
057 * @author Grahame Grieve
058 *
059 */
060public class Element extends Base {
061
062
063  public enum SpecialElement {
064                CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
065
066    public static SpecialElement fromProperty(Property property) {
067      if (property.getStructure().getIdElement().getIdPart().equals("Parameters"))
068        return PARAMETER;
069      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("resource"))
070        return BUNDLE_ENTRY;
071      if (property.getStructure().getIdElement().getIdPart().equals("Bundle") && property.getName().equals("outcome"))
072        return BUNDLE_OUTCOME;
073      if (property.getName().equals("contained")) 
074        return CONTAINED;
075      throw new Error("Unknown resource containing a native resource: "+property.getDefinition().getId());
076    }
077        }
078
079        private List<String> comments;// not relevant for production, but useful in documentation
080        private String name;
081        private String type;
082        private String value;
083        private int index = -1;
084        private List<Element> children;
085        private Property property;
086  private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places
087        private int line;
088        private int col;
089        private SpecialElement special;
090        private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
091        private String explicitType; // for xsi:type attribute
092
093        public Element(String name) {
094                super();
095                this.name = name;
096        }
097
098  public Element(Element other) {
099    super();
100    name = other.name;
101    type = other.type;
102    property = other.property;
103    elementProperty = other.elementProperty;
104    special = other.special;
105  }
106  
107  public Element(String name, Property property) {
108                super();
109                this.name = name;
110                this.property = property;
111        }
112
113        public Element(String name, Property property, String type, String value) {
114                super();
115                this.name = name;
116                this.property = property;
117                this.type = type;
118                this.value = value;
119        }
120
121        public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
122                this.property = property;
123    this.elementProperty = elementProperty;
124                this.special = special;
125        }
126
127        public SpecialElement getSpecial() {
128                return special;
129        }
130
131        public String getName() {
132                return name;
133        }
134
135        public String getType() {
136                if (type == null)
137                        return property.getType(name);
138                else
139                  return type;
140        }
141
142        public String getValue() {
143                return value;
144        }
145
146        public boolean hasChildren() {
147                return !(children == null || children.isEmpty());
148        }
149
150        public List<Element> getChildren() {
151                if (children == null)
152                        children = new ArrayList<Element>();
153                return children;
154        }
155
156        public boolean hasComments() {
157                return !(comments == null || comments.isEmpty());
158        }
159
160        public List<String> getComments() {
161                if (comments == null)
162                        comments = new ArrayList<String>();
163                return comments;
164        }
165
166        public Property getProperty() {
167                return property;
168        }
169
170        public void setValue(String value) {
171                this.value = value;
172        }
173
174        public void setType(String type) {
175                this.type = type;
176
177        }
178
179        public boolean hasValue() {
180                return value != null;
181        }
182
183        public List<Element> getChildrenByName(String name) {
184                List<Element> res = new ArrayList<Element>();
185                if (hasChildren()) {
186                        for (Element child : children)
187                                if (name.equals(child.getName()))
188                                        res.add(child);
189                }
190                return res;
191        }
192
193        public void numberChildren() {
194                if (children == null)
195                        return;
196                
197                String last = "";
198                int index = 0;
199                for (Element child : children) {
200                        if (child.getProperty().isList()) {
201                          if (last.equals(child.getName())) {
202                                index++;
203                          } else {
204                                last = child.getName();
205                                index = 0;
206                          }
207                        child.index = index;
208                        } else {
209                                child.index = -1;
210                        }
211                        child.numberChildren();
212                }       
213        }
214
215        public int getIndex() {
216                return index;
217        }
218
219        public boolean hasIndex() {
220                return index > -1;
221        }
222
223        public void setIndex(int index) {
224                this.index = index;
225        }
226
227        public String getChildValue(String name) {
228                if (children == null)
229                        return null;
230                for (Element child : children) {
231                        if (name.equals(child.getName()))
232                                return child.getValue();
233                }
234        return null;
235        }
236
237  public void setChildValue(String name, String value) {
238    if (children == null)
239      children = new ArrayList<Element>();
240    for (Element child : children) {
241      if (name.equals(child.getName())) {
242        if (!child.isPrimitive())
243          throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
244        child.setValue(value);
245      }
246    }
247    try {
248      setProperty(name.hashCode(), name, new StringType(value));
249    } catch (FHIRException e) {
250      throw new Error(e);
251    }
252  }
253
254        public List<Element> getChildren(String name) {
255                List<Element> res = new ArrayList<Element>(); 
256                if (children != null)
257                for (Element child : children) {
258                        if (name.equals(child.getName()))
259                                res.add(child);
260                }
261                return res;
262        }
263
264  public boolean hasType() {
265    if (type == null)
266      return property.hasType(name);
267    else
268      return true;
269  }
270
271  @Override
272  public String fhirType() {
273    return getType();
274  }
275
276  @Override
277        public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
278        if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
279//              String tn = getType();
280//              throw new Error(tn+" not done yet");
281          Base[] b = new Base[1];
282          b[0] = new StringType(value);
283          return b;
284        }
285                
286        List<Base> result = new ArrayList<Base>();
287        if (children != null) {
288        for (Element child : children) {
289                if (child.getName().equals(name))
290                        result.add(child);
291                if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]"))
292                        result.add(child);
293        }
294        }
295        if (result.isEmpty() && checkValid) {
296//              throw new FHIRException("not determined yet");
297        }
298        return result.toArray(new Base[result.size()]);
299        }
300
301        @Override
302        protected void listChildren(List<org.hl7.fhir.r4.model.Property> childProps) {
303          if (children != null) {
304            Map<String, org.hl7.fhir.r4.model.Property> map = new HashMap<String, org.hl7.fhir.r4.model.Property>();
305            for (Element c : children) {
306              org.hl7.fhir.r4.model.Property p = map.get(c.getName());
307              if (p == null) {
308              p = new org.hl7.fhir.r4.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c);
309          childProps.add(p);
310          map.put(c.getName(), p);
311              
312              } else
313                p.getValues().add(c);
314            }
315          }
316        }
317        
318  @Override
319  public Base setProperty(int hash, String name, Base value) throws FHIRException {
320    if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
321      this.xhtml = castToXhtml(value);
322      this.value =  castToXhtmlString(value);
323      return this;
324    }
325    if (isPrimitive() && (hash == "value".hashCode())) {
326      this.value = castToString(value).asStringValue();
327      return this;
328    }
329    
330    if (!value.isPrimitive() && !(value instanceof Element)) {
331      if (isDataType(value)) 
332        value = convertToElement(property.getChild(name), value);
333      else
334        throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
335    }
336    
337    if (children == null)
338      children = new ArrayList<Element>();
339    Element childForValue = null;
340    
341    // look through existing children
342    for (Element child : children) {
343      if (child.getName().equals(name)) {
344        if (!child.isList()) {
345          childForValue = child;
346          break;
347        } else {
348          Element ne = new Element(child);
349          children.add(ne);
350          numberChildren();
351          childForValue = ne;
352          break;
353        }
354      }
355    }
356
357    int i = 0;
358    if (childForValue == null)
359      for (Property p : property.getChildProperties(this.name, type)) {
360        int t = -1;
361        for (int c =0; c < children.size(); c++) {
362          Element e = children.get(c);
363          if (p.getName().equals(e.getName()))
364            t = c;
365        }
366        if (t > i)
367          i = t;
368        if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
369          Element ne = new Element(name, p);
370          children.add(i, ne);
371          childForValue = ne;
372          break;
373        }
374      }
375    
376    if (childForValue == null)
377      throw new Error("Cannot set property "+name+" on "+this.name);
378    else if (value.isPrimitive()) {
379      if (childForValue.property.getName().endsWith("[x]"))
380        childForValue.name = name+Utilities.capitalize(value.fhirType());
381      childForValue.setValue(value.primitiveValue());
382    } else {
383      Element ve = (Element) value;
384      childForValue.type = ve.getType();
385      if (childForValue.property.getName().endsWith("[x]"))
386        childForValue.name = name+Utilities.capitalize(childForValue.type);
387      else if (value.isResource()) {
388        if (childForValue.elementProperty == null)
389          childForValue.elementProperty = childForValue.property;
390        childForValue.property = ve.property;
391        childForValue.special = SpecialElement.BUNDLE_ENTRY;
392      }
393      if (ve.children != null) {
394        if (childForValue.children == null)
395          childForValue.children = new ArrayList<Element>();
396        else 
397          childForValue.children.clear();
398        childForValue.children.addAll(ve.children);
399      }
400    }
401    return childForValue;
402  }
403
404  private Base convertToElement(Property prop, Base v) throws FHIRException {
405    return new ObjectConverter(property.getContext()).convert(prop, (Type) v);
406  }
407
408  private boolean isDataType(Base v) {
409    return v instanceof Type &&  property.getContext().getTypeNames().contains(v.fhirType());
410  }
411
412  @Override
413  public Base makeProperty(int hash, String name) throws FHIRException {
414    if (isPrimitive() && (hash == "value".hashCode())) {
415      return new StringType(value);
416    }
417
418    if (children == null)
419      children = new ArrayList<Element>();
420    
421    // look through existing children
422    for (Element child : children) {
423      if (child.getName().equals(name)) {
424        if (!child.isList()) {
425          return child;
426        } else {
427          Element ne = new Element(child);
428          children.add(ne);
429          numberChildren();
430          return ne;
431        }
432      }
433    }
434
435    for (Property p : property.getChildProperties(this.name, type)) {
436      if (p.getName().equals(name)) {
437        Element ne = new Element(name, p);
438        children.add(ne);
439        return ne;
440      }
441    }
442      
443    throw new Error("Unrecognised name "+name+" on "+this.name); 
444  }
445  
446        private int maxToInt(String max) {
447    if (max.equals("*"))
448      return Integer.MAX_VALUE;
449    else
450      return Integer.parseInt(max);
451        }
452
453        @Override
454        public boolean isPrimitive() {
455                return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
456        }
457        
458  @Override
459  public boolean isBooleanPrimitive() {
460    return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name)));
461  }
462 
463  @Override
464  public boolean isResource() {
465    return property.isResource();
466  }
467  
468
469        @Override
470        public boolean hasPrimitiveValue() {
471                return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
472        }
473        
474
475        @Override
476        public String primitiveValue() {
477                if (isPrimitive())
478                  return value;
479                else {
480                        if (hasPrimitiveValue() && children != null) {
481                                for (Element c : children) {
482                                        if (c.getName().equals("value"))
483                                                return c.primitiveValue();
484                                }
485                        }
486                        return null;
487                }
488        }
489        
490        // for the validator
491  public int line() {
492    return line;
493  }
494
495  public int col() {
496    return col;
497  }
498
499        public Element markLocation(int line, int col) {
500                this.line = line;
501                this.col = col; 
502                return this;
503        }
504
505        public void clearDecorations() {
506          clearUserData("fhir.decorations");
507          for (Element e : children)
508            e.clearDecorations();         
509        }
510        
511        public void markValidation(StructureDefinition profile, ElementDefinition definition) {
512          @SuppressWarnings("unchecked")
513    List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations");
514          if (decorations == null) {
515            decorations = new ArrayList<>();
516            setUserData("fhir.decorations", decorations);
517          }
518          decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getUserString("path"), definition.getPath()));
519          if (definition.getId() != null && tail(definition.getId()).contains(":")) {
520            String[] details = tail(definition.getId()).split(":");
521            decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1]));
522          }
523        }
524        
525  private String tail(String id) {
526    return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id;
527  }
528
529  public Element getNamedChild(String name) {
530          if (children == null)
531                return null;
532          Element result = null;
533          for (Element child : children) {
534                if (child.getName().equals(name)) {
535                        if (result == null)
536                                result = child;
537                        else 
538                                throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
539                }
540          }
541          return result;
542        }
543
544  public void getNamedChildren(String name, List<Element> list) {
545        if (children != null)
546                for (Element child : children) 
547                        if (child.getName().equals(name))
548                                list.add(child);
549  }
550
551  public String getNamedChildValue(String name) {
552        Element child = getNamedChild(name);
553        return child == null ? null : child.value;
554  }
555
556  public void getNamedChildrenWithWildcard(String string, List<Element> values) {
557          Validate.isTrue(string.endsWith("[x]"));
558          
559          String start = string.substring(0, string.length() - 3);
560                if (children != null) {
561                        for (Element child : children) { 
562                                if (child.getName().startsWith(start)) {
563                                        values.add(child);
564                                }
565                        }
566                }
567  }
568
569  
570        public XhtmlNode getXhtml() {
571                return xhtml;
572        }
573
574        public Element setXhtml(XhtmlNode xhtml) {
575                this.xhtml = xhtml;
576                return this;
577        }
578
579        @Override
580        public boolean isEmpty() {
581        // GG: this used to also test !"".equals(value). 
582    // the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath.
583          // it should not cause any problems in real life.
584                if (value != null) {   
585                        return false;
586                }
587                for (Element next : getChildren()) {
588                        if (!next.isEmpty()) {
589                                return false;
590                        }
591                }
592                return true;
593        }
594
595  public Property getElementProperty() {
596    return elementProperty;
597  }
598
599  public boolean hasElementProperty() {
600    return elementProperty != null;
601  }
602
603  public boolean hasChild(String name) {
604    return getNamedChild(name) != null;
605  }
606
607  public boolean hasChildren(String name) {
608    if (children != null)
609      for (Element child : children) 
610        if (child.getName().equals(name))
611          return true;
612    return false;
613  }
614
615  @Override
616  public String toString() {
617    return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
618  }
619
620  @Override
621  public String getIdBase() {
622    return getChildValue("id");
623  }
624
625  @Override
626  public void setIdBase(String value) {
627    setChildValue("id", value);
628  }
629
630
631  @Override
632  public boolean equalsDeep(Base other) {
633    if (!super.equalsDeep(other))
634      return false;
635    if (isPrimitive() && other.isPrimitive())
636      return primitiveValue().equals(other.primitiveValue());
637    if (isPrimitive() || other.isPrimitive())
638      return false;
639    Set<String> processed  = new HashSet<String>();
640    for (org.hl7.fhir.r4.model.Property p : children()) {
641      String name = p.getName();
642      processed.add(name);
643      org.hl7.fhir.r4.model.Property o = other.getChildByName(name);
644      if (!equalsDeep(p, o))
645        return false;
646    }
647    for (org.hl7.fhir.r4.model.Property p : children()) {
648      String name = p.getName();
649      if (!processed.contains(name)) {
650        org.hl7.fhir.r4.model.Property o = other.getChildByName(name);
651        if (!equalsDeep(p, o))
652          return false;
653      }
654    }
655    return true;
656  }
657
658  private boolean equalsDeep(org.hl7.fhir.r4.model.Property p, org.hl7.fhir.r4.model.Property o) {
659    if (o == null || p == null)
660      return false;
661    if (p.getValues().size() != o.getValues().size())
662      return false;
663    for (int i = 0; i < p.getValues().size(); i++)
664      if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
665        return false;
666    return true;
667  }
668
669  @Override
670  public boolean equalsShallow(Base other) {
671    if (!super.equalsShallow(other))
672      return false;
673    if (isPrimitive() && other.isPrimitive())
674      return primitiveValue().equals(other.primitiveValue());
675    if (isPrimitive() || other.isPrimitive())
676      return false;
677    return true; //?
678  }
679
680  public Type asType() throws FHIRException {
681    return new ObjectConverter(property.getContext()).convertToType(this);
682  }
683
684  @Override
685  public boolean isMetadataBased() {
686    return true;
687  }
688
689  public boolean isList() {
690    if (elementProperty != null)
691      return elementProperty.isList();
692    else
693      return property.isList();
694  }
695  
696  @Override
697  public String[] getTypesForProperty(int hash, String name) throws FHIRException {
698    Property p = property.getChildSimpleName(this.name, name);
699    if (p != null) {
700      Set<String> types = new HashSet<String>();
701      for (TypeRefComponent tr : p.getDefinition().getType()) {
702        types.add(tr.getCode());
703      }
704      return types.toArray(new String[]{});
705    }
706    return super.getTypesForProperty(hash, name);
707
708  }
709
710  public void sort() {
711    if (children != null) {
712      List<Element> remove = new ArrayList<Element>();
713      for (Element child : children) {
714        child.sort();
715        if (child.isEmpty())
716          remove.add(child);
717      }
718      children.removeAll(remove);
719      Collections.sort(children, new ElementSortComparator(this, this.property));
720    }
721  }
722
723  public class ElementSortComparator implements Comparator<Element> {
724    private List<ElementDefinition> children;
725    public ElementSortComparator(Element e, Property property) {
726      String tn = e.getType();
727      StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, property.getContext().getOverrideVersionNs()));
728      if (sd != null && !sd.getAbstract())
729        children = sd.getSnapshot().getElement();
730      else
731        children = property.getStructure().getSnapshot().getElement();
732    }
733    
734    @Override
735    public int compare(Element e0, Element e1) {
736      int i0 = find(e0);
737      int i1 = find(e1);
738      return Integer.compare(i0, i1);
739    }
740    private int find(Element e0) {
741      int i =  e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) :  children.indexOf(e0.property.getDefinition());
742      return i; 
743    }
744
745  }
746
747  public class ICodingImpl implements ICoding {
748    private String system;
749    private String version;
750    private String code;
751    private String display;
752    private boolean doesSystem;
753    private boolean doesVersion;
754    private boolean doesCode;
755    private boolean doesDisplay;
756    public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) {
757      super();
758      this.doesCode = doesCode;
759      this.doesSystem = doesSystem;
760      this.doesVersion = doesVersion;
761      this.doesDisplay = doesDisplay;
762    }
763    public String getSystem() {
764      return system;
765    }
766    public String getVersion() {
767      return version;
768    }
769    public String getCode() {
770      return code;
771    }
772    public String getDisplay() {
773      return display;
774    }
775    public boolean hasSystem() {
776      return !Utilities.noString(system); 
777    }
778    public boolean hasVersion() {
779      return !Utilities.noString(version);
780    }
781    public boolean hasCode() {
782      return !Utilities.noString(code);
783    }
784    public boolean hasDisplay() {
785      return !Utilities.noString(display);
786    }
787    public boolean supportsSystem() {
788      return doesSystem;
789    }
790    public boolean supportsVersion() {
791      return doesVersion;
792    }
793    public boolean supportsCode() {
794      return doesCode;
795    }
796    public boolean supportsDisplay() {
797      return doesDisplay;
798    }    
799  }
800
801  public ICoding getAsICoding() throws FHIRException {
802    if ("code".equals(fhirType())) {
803      if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED)
804        return null;
805      ICodingImpl c = new ICodingImpl(true, true, false, false);
806      c.code = primitiveValue();
807      ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getDefinition().getBinding(), true, false);
808      if (vse.getValueset() == null)
809        return null;
810      for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
811        if (cc.getCode().equals(c.code)) {
812          c.system = cc.getSystem();
813          if (cc.hasVersion()) {
814            c.doesVersion = true;
815            c.version = cc.getVersion();
816          }
817          if (cc.hasDisplay()) {
818            c.doesDisplay = true;
819            c.display = cc.getDisplay();
820          }
821        }
822      }
823      if (c.system == null)
824        return null;
825      return c;   
826    } else if ("Coding".equals(fhirType())) {
827      ICodingImpl c = new ICodingImpl(true, true, true, true);
828      c.system = getNamedChildValue("system");
829      c.code = getNamedChildValue("code");
830      c.display = getNamedChildValue("display");
831      c.version = getNamedChildValue("version");
832      return c;
833    } else if ("Quantity".equals(fhirType())) {
834      ICodingImpl c = new ICodingImpl(true, true, false, false);
835      c.system = getNamedChildValue("system");
836      c.code = getNamedChildValue("code");
837      return c;
838    } else 
839      return null;
840  }
841
842  public String getExplicitType() {
843    return explicitType;
844  }
845
846  public void setExplicitType(String explicitType) {
847    this.explicitType = explicitType;
848  }
849
850  
851}