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