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