001package org.hl7.fhir.r4.model; 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.utilities.SourceLocation; 038import org.hl7.fhir.utilities.Utilities; 039 040public class ExpressionNode { 041 042 public enum Kind { 043 Name, Function, Constant, Group, Unary 044 } 045 public enum Function { 046 Custom, 047 048 Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single, 049 First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, ReplaceMatches, Contains, Replace, Length, 050 Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue, 051 HasValue, AliasAs, Alias, HtmlChecks, OfType, Type, 052 ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo; 053 054 public static Function fromCode(String name) { 055 if (name.equals("empty")) return Function.Empty; 056 if (name.equals("not")) return Function.Not; 057 if (name.equals("exists")) return Function.Exists; 058 if (name.equals("subsetOf")) return Function.SubsetOf; 059 if (name.equals("supersetOf")) return Function.SupersetOf; 060 if (name.equals("isDistinct")) return Function.IsDistinct; 061 if (name.equals("distinct")) return Function.Distinct; 062 if (name.equals("count")) return Function.Count; 063 if (name.equals("where")) return Function.Where; 064 if (name.equals("select")) return Function.Select; 065 if (name.equals("all")) return Function.All; 066 if (name.equals("repeat")) return Function.Repeat; 067 if (name.equals("aggregate")) return Function.Aggregate; 068 if (name.equals("item")) return Function.Item; 069 if (name.equals("as")) return Function.As; 070 if (name.equals("is")) return Function.Is; 071 if (name.equals("single")) return Function.Single; 072 if (name.equals("first")) return Function.First; 073 if (name.equals("last")) return Function.Last; 074 if (name.equals("tail")) return Function.Tail; 075 if (name.equals("skip")) return Function.Skip; 076 if (name.equals("take")) return Function.Take; 077 if (name.equals("union")) return Function.Union; 078 if (name.equals("combine")) return Function.Combine; 079 if (name.equals("intersect")) return Function.Intersect; 080 if (name.equals("exclude")) return Function.Exclude; 081 if (name.equals("iif")) return Function.Iif; 082 if (name.equals("lower")) return Function.Lower; 083 if (name.equals("upper")) return Function.Upper; 084 if (name.equals("toChars")) return Function.ToChars; 085 if (name.equals("indexOf")) return Function.IndexOf; 086 if (name.equals("substring")) return Function.Substring; 087 if (name.equals("startsWith")) return Function.StartsWith; 088 if (name.equals("endsWith")) return Function.EndsWith; 089 if (name.equals("matches")) return Function.Matches; 090 if (name.equals("replaceMatches")) return Function.ReplaceMatches; 091 if (name.equals("contains")) return Function.Contains; 092 if (name.equals("replace")) return Function.Replace; 093 if (name.equals("length")) return Function.Length; 094 if (name.equals("children")) return Function.Children; 095 if (name.equals("descendants")) return Function.Descendants; 096 if (name.equals("memberOf")) return Function.MemberOf; 097 if (name.equals("trace")) return Function.Trace; 098 if (name.equals("check")) return Function.Check; 099 if (name.equals("today")) return Function.Today; 100 if (name.equals("now")) return Function.Now; 101 if (name.equals("resolve")) return Function.Resolve; 102 if (name.equals("extension")) return Function.Extension; 103 if (name.equals("allFalse")) return Function.AllFalse; 104 if (name.equals("anyFalse")) return Function.AnyFalse; 105 if (name.equals("allTrue")) return Function.AllTrue; 106 if (name.equals("anyTrue")) return Function.AnyTrue; 107 if (name.equals("hasValue")) return Function.HasValue; 108 if (name.equals("alias")) return Function.Alias; 109 if (name.equals("aliasAs")) return Function.AliasAs; 110 if (name.equals("htmlChecks")) return Function.HtmlChecks; 111 if (name.equals("ofType")) return Function.OfType; 112 if (name.equals("type")) return Function.Type; 113 if (name.equals("toInteger")) return Function.ToInteger; 114 if (name.equals("toDecimal")) return Function.ToDecimal; 115 if (name.equals("toString")) return Function.ToString; 116 if (name.equals("toQuantity")) return Function.ToQuantity; 117 if (name.equals("toBoolean")) return Function.ToBoolean; 118 if (name.equals("toDateTime")) return Function.ToDateTime; 119 if (name.equals("toTime")) return Function.ToTime; 120 if (name.equals("convertsToInteger")) return Function.ConvertsToInteger; 121 if (name.equals("convertsToDecimal")) return Function.ConvertsToDecimal; 122 if (name.equals("convertsToString")) return Function.ConvertsToString; 123 if (name.equals("convertsToQuantity")) return Function.ConvertsToQuantity; 124 if (name.equals("convertsToBoolean")) return Function.ConvertsToBoolean; 125 if (name.equals("convertsToDateTime")) return Function.ConvertsToDateTime; 126 if (name.equals("convertsToTime")) return Function.ConvertsToTime; 127 if (name.equals("conformsTo")) return Function.ConformsTo; 128 return null; 129 } 130 public String toCode() { 131 switch (this) { 132 case Empty : return "empty"; 133 case Not : return "not"; 134 case Exists : return "exists"; 135 case SubsetOf : return "subsetOf"; 136 case SupersetOf : return "supersetOf"; 137 case IsDistinct : return "isDistinct"; 138 case Distinct : return "distinct"; 139 case Count : return "count"; 140 case Where : return "where"; 141 case Select : return "select"; 142 case All : return "all"; 143 case Repeat : return "repeat"; 144 case Aggregate : return "aggregate"; 145 case Item : return "item"; 146 case As : return "as"; 147 case Is : return "is"; 148 case Single : return "single"; 149 case First : return "first"; 150 case Last : return "last"; 151 case Tail : return "tail"; 152 case Skip : return "skip"; 153 case Take : return "take"; 154 case Union : return "union"; 155 case Combine : return "combine"; 156 case Intersect : return "intersect"; 157 case Exclude : return "exclude"; 158 case Iif : return "iif"; 159 case ToChars : return "toChars"; 160 case Lower : return "lower"; 161 case Upper : return "upper"; 162 case IndexOf : return "indexOf"; 163 case Substring : return "substring"; 164 case StartsWith : return "startsWith"; 165 case EndsWith : return "endsWith"; 166 case Matches : return "matches"; 167 case ReplaceMatches : return "replaceMatches"; 168 case Contains : return "contains"; 169 case Replace : return "replace"; 170 case Length : return "length"; 171 case Children : return "children"; 172 case Descendants : return "descendants"; 173 case MemberOf : return "memberOf"; 174 case Trace : return "trace"; 175 case Check : return "check"; 176 case Today : return "today"; 177 case Now : return "now"; 178 case Resolve : return "resolve"; 179 case Extension : return "extension"; 180 case AllFalse : return "allFalse"; 181 case AnyFalse : return "anyFalse"; 182 case AllTrue : return "allTrue"; 183 case AnyTrue : return "anyTrue"; 184 case HasValue : return "hasValue"; 185 case Alias : return "alias"; 186 case AliasAs : return "aliasAs"; 187 case HtmlChecks : return "htmlChecks"; 188 case OfType : return "ofType"; 189 case Type : return "type"; 190 case ToInteger : return "toInteger"; 191 case ToDecimal : return "toDecimal"; 192 case ToString : return "toString"; 193 case ToBoolean : return "toBoolean"; 194 case ToQuantity : return "toQuantity"; 195 case ToDateTime : return "toDateTime"; 196 case ToTime : return "toTime"; 197 case ConvertsToInteger : return "convertsToInteger"; 198 case ConvertsToDecimal : return "convertsToDecimal"; 199 case ConvertsToString : return "convertsToString"; 200 case ConvertsToBoolean : return "convertsToBoolean"; 201 case ConvertsToQuantity : return "convertsToQuantity"; 202 case ConvertsToDateTime : return "convertsToDateTime"; 203 case ConvertsToTime : return "isTime"; 204 case ConformsTo : return "conformsTo"; 205 default: return "??"; 206 } 207 } 208 } 209 210 public enum Operation { 211 Equals, Equivalent, NotEquals, NotEquivalent, LessThan, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, And, Xor, Implies, 212 Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains, MemberOf; 213 214 public static Operation fromCode(String name) { 215 if (Utilities.noString(name)) 216 return null; 217 if (name.equals("=")) 218 return Operation.Equals; 219 if (name.equals("~")) 220 return Operation.Equivalent; 221 if (name.equals("!=")) 222 return Operation.NotEquals; 223 if (name.equals("!~")) 224 return Operation.NotEquivalent; 225 if (name.equals(">")) 226 return Operation.Greater; 227 if (name.equals("<")) 228 return Operation.LessThan; 229 if (name.equals(">=")) 230 return Operation.GreaterOrEqual; 231 if (name.equals("<=")) 232 return Operation.LessOrEqual; 233 if (name.equals("|")) 234 return Operation.Union; 235 if (name.equals("or")) 236 return Operation.Or; 237 if (name.equals("and")) 238 return Operation.And; 239 if (name.equals("xor")) 240 return Operation.Xor; 241 if (name.equals("is")) 242 return Operation.Is; 243 if (name.equals("as")) 244 return Operation.As; 245 if (name.equals("*")) 246 return Operation.Times; 247 if (name.equals("/")) 248 return Operation.DivideBy; 249 if (name.equals("+")) 250 return Operation.Plus; 251 if (name.equals("-")) 252 return Operation.Minus; 253 if (name.equals("&")) 254 return Operation.Concatenate; 255 if (name.equals("implies")) 256 return Operation.Implies; 257 if (name.equals("div")) 258 return Operation.Div; 259 if (name.equals("mod")) 260 return Operation.Mod; 261 if (name.equals("in")) 262 return Operation.In; 263 if (name.equals("contains")) 264 return Operation.Contains; 265 if (name.equals("memberOf")) 266 return Operation.MemberOf; 267 return null; 268 269 } 270 public String toCode() { 271 switch (this) { 272 case Equals : return "="; 273 case Equivalent : return "~"; 274 case NotEquals : return "!="; 275 case NotEquivalent : return "!~"; 276 case Greater : return ">"; 277 case LessThan : return "<"; 278 case GreaterOrEqual : return ">="; 279 case LessOrEqual : return "<="; 280 case Union : return "|"; 281 case Or : return "or"; 282 case And : return "and"; 283 case Xor : return "xor"; 284 case Times : return "*"; 285 case DivideBy : return "/"; 286 case Plus : return "+"; 287 case Minus : return "-"; 288 case Concatenate : return "&"; 289 case Implies : return "implies"; 290 case Is : return "is"; 291 case As : return "as"; 292 case Div : return "div"; 293 case Mod : return "mod"; 294 case In : return "in"; 295 case Contains : return "contains"; 296 case MemberOf : return "memberOf"; 297 default: return "??"; 298 } 299 } 300 } 301 302 public enum CollectionStatus { 303 SINGLETON, ORDERED, UNORDERED; 304 } 305 306 //the expression will have one of either name or constant 307 private String uniqueId; 308 private Kind kind; 309 private String name; 310 private Base constant; 311 private Function function; 312 private List<ExpressionNode> parameters; // will be created if there is a function 313 private ExpressionNode inner; 314 private ExpressionNode group; 315 private Operation operation; 316 private boolean proximal; // a proximal operation is the first in the sequence of operations. This is significant when evaluating the outcomes 317 private ExpressionNode opNext; 318 private SourceLocation start; 319 private SourceLocation end; 320 private SourceLocation opStart; 321 private SourceLocation opEnd; 322 private TypeDetails types; 323 private TypeDetails opTypes; 324 325 326 public ExpressionNode(int uniqueId) { 327 super(); 328 this.uniqueId = Integer.toString(uniqueId); 329 } 330 331 public String toString() { 332 StringBuilder b = new StringBuilder(); 333 switch (kind) { 334 case Name: 335 b.append(name); 336 break; 337 case Function: 338 if (function == Function.Item) 339 b.append("["); 340 else { 341 b.append(name); 342 b.append("("); 343 } 344 boolean first = true; 345 for (ExpressionNode n : parameters) { 346 if (first) 347 first = false; 348 else 349 b.append(", "); 350 b.append(n.toString()); 351 } 352 if (function == Function.Item) { 353 b.append("]"); 354 } else { 355 b.append(")"); 356 } 357 break; 358 case Constant: 359 if (constant == null) { 360 b.append("{}"); 361 } else if (constant instanceof StringType) { 362 b.append("'" + Utilities.escapeJson(constant.primitiveValue()) + "'"); 363 } else if (constant instanceof Quantity) { 364 Quantity q = (Quantity) constant; 365 b.append(Utilities.escapeJson(q.getValue().toPlainString())); 366 b.append(" '"); 367 b.append(Utilities.escapeJson(q.getUnit())); 368 b.append("'"); 369 } else if (constant.primitiveValue() != null) { 370 b.append(Utilities.escapeJson(constant.primitiveValue())); 371 } else { 372 b.append(Utilities.escapeJson(constant.toString())); 373 } 374 break; 375 case Group: 376 b.append("("); 377 b.append(group.toString()); 378 b.append(")"); 379 } 380 if (inner != null) { 381 if (!((ExpressionNode.Kind.Function == inner.getKind()) && (ExpressionNode.Function.Item == inner.getFunction()))) { 382 b.append("."); 383 } 384 b.append(inner.toString()); 385 } 386 if (operation != null) { 387 b.append(" "); 388 b.append(operation.toCode()); 389 b.append(" "); 390 b.append(opNext.toString()); 391 } 392 393 return b.toString(); 394 } 395 396 public String getName() { 397 return name; 398 } 399 public void setName(String name) { 400 this.name = name; 401 } 402 public Base getConstant() { 403 return constant; 404 } 405 public void setConstant(Base constant) { 406 this.constant = constant; 407 } 408 409 public Function getFunction() { 410 return function; 411 } 412 public void setFunction(Function function) { 413 this.function = function; 414 if (parameters == null) 415 parameters = new ArrayList<ExpressionNode>(); 416 } 417 418 public boolean isProximal() { 419 return proximal; 420 } 421 public void setProximal(boolean proximal) { 422 this.proximal = proximal; 423 } 424 public Operation getOperation() { 425 return operation; 426 } 427 public void setOperation(Operation operation) { 428 this.operation = operation; 429 } 430 public ExpressionNode getInner() { 431 return inner; 432 } 433 public void setInner(ExpressionNode value) { 434 this.inner = value; 435 } 436 public ExpressionNode getOpNext() { 437 return opNext; 438 } 439 public void setOpNext(ExpressionNode value) { 440 this.opNext = value; 441 } 442 public List<ExpressionNode> getParameters() { 443 return parameters; 444 } 445 public boolean checkName() { 446 if (!name.startsWith("$")) 447 return true; 448 else 449 return Utilities.existsInList(name, "$this", "$total"); 450 } 451 452 public Kind getKind() { 453 return kind; 454 } 455 456 public void setKind(Kind kind) { 457 this.kind = kind; 458 } 459 460 public ExpressionNode getGroup() { 461 return group; 462 } 463 464 public void setGroup(ExpressionNode group) { 465 this.group = group; 466 } 467 468 public SourceLocation getStart() { 469 return start; 470 } 471 472 public void setStart(SourceLocation start) { 473 this.start = start; 474 } 475 476 public SourceLocation getEnd() { 477 return end; 478 } 479 480 public void setEnd(SourceLocation end) { 481 this.end = end; 482 } 483 484 public SourceLocation getOpStart() { 485 return opStart; 486 } 487 488 public void setOpStart(SourceLocation opStart) { 489 this.opStart = opStart; 490 } 491 492 public SourceLocation getOpEnd() { 493 return opEnd; 494 } 495 496 public void setOpEnd(SourceLocation opEnd) { 497 this.opEnd = opEnd; 498 } 499 500 public String getUniqueId() { 501 return uniqueId; 502 } 503 504 505 public int parameterCount() { 506 if (parameters == null) 507 return 0; 508 else 509 return parameters.size(); 510 } 511 512 public String Canonical() { 513 StringBuilder b = new StringBuilder(); 514 write(b); 515 return b.toString(); 516 } 517 518 public String summary() { 519 switch (kind) { 520 case Name: return uniqueId+": "+name; 521 case Function: return uniqueId+": "+function.toString()+"()"; 522 case Constant: return uniqueId+": "+constant; 523 case Group: return uniqueId+": (Group)"; 524 } 525 return "??"; 526 } 527 528 private void write(StringBuilder b) { 529 530 switch (kind) { 531 case Name: 532 b.append(name); 533 break; 534 case Constant: 535 b.append(constant); 536 break; 537 case Function: 538 b.append(function.toCode()); 539 b.append('('); 540 boolean f = true; 541 for (ExpressionNode n : parameters) { 542 if (f) 543 f = false; 544 else 545 b.append(", "); 546 n.write(b); 547 } 548 b.append(')'); 549 550 break; 551 case Group: 552 b.append('('); 553 group.write(b); 554 b.append(')'); 555 } 556 557 if (inner != null) { 558 b.append('.'); 559 inner.write(b); 560 } 561 if (operation != null) { 562 b.append(' '); 563 b.append(operation.toCode()); 564 b.append(' '); 565 opNext.write(b); 566 } 567 } 568 569 public String check() { 570 571 switch (kind) { 572 case Name: 573 if (Utilities.noString(name)) 574 return "No Name provided @ "+location(); 575 break; 576 577 case Function: 578 if (function == null) 579 return "No Function id provided @ "+location(); 580 for (ExpressionNode n : parameters) { 581 String msg = n.check(); 582 if (msg != null) 583 return msg; 584 } 585 586 break; 587 588 case Unary: 589 break; 590 case Constant: 591 if (constant == null) 592 return "No Constant provided @ "+location(); 593 break; 594 595 case Group: 596 if (group == null) 597 return "No Group provided @ "+location(); 598 else { 599 String msg = group.check(); 600 if (msg != null) 601 return msg; 602 } 603 } 604 if (inner != null) { 605 String msg = inner.check(); 606 if (msg != null) 607 return msg; 608 } 609 if (operation == null) { 610 611 if (opNext != null) 612 return "Next provided when it shouldn't be @ "+location(); 613 } 614 else { 615 if (opNext == null) 616 return "No Next provided @ "+location(); 617 else 618 opNext.check(); 619 } 620 return null; 621 622 } 623 624 private String location() { 625 return Integer.toString(start.getLine())+", "+Integer.toString(start.getColumn()); 626 } 627 628 public TypeDetails getTypes() { 629 return types; 630 } 631 632 public void setTypes(TypeDetails types) { 633 this.types = types; 634 } 635 636 public TypeDetails getOpTypes() { 637 return opTypes; 638 } 639 640 public void setOpTypes(TypeDetails opTypes) { 641 this.opTypes = opTypes; 642 } 643 644}