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