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 instanceof StringType) 360 b.append("'"+Utilities.escapeJson(constant.primitiveValue())+"'"); 361 else if (constant instanceof Quantity) { 362 Quantity q = (Quantity) constant; 363 b.append(Utilities.escapeJson(q.getValue().toPlainString())); 364 b.append(" '"); 365 b.append(Utilities.escapeJson(q.getUnit())); 366 b.append("'"); 367 } else if (constant.primitiveValue() != null) 368 b.append(Utilities.escapeJson(constant.primitiveValue())); 369 else 370 b.append(Utilities.escapeJson(constant.toString())); 371 break; 372 case Group: 373 b.append("("); 374 b.append(group.toString()); 375 b.append(")"); 376 } 377 if (inner != null) { 378 if (!((ExpressionNode.Kind.Function == inner.getKind()) && (ExpressionNode.Function.Item == inner.getFunction()))) { 379 b.append("."); 380 } 381 b.append(inner.toString()); 382 } 383 if (operation != null) { 384 b.append(" "); 385 b.append(operation.toCode()); 386 b.append(" "); 387 b.append(opNext.toString()); 388 } 389 390 return b.toString(); 391 } 392 393 public String getName() { 394 return name; 395 } 396 public void setName(String name) { 397 this.name = name; 398 } 399 public Base getConstant() { 400 return constant; 401 } 402 public void setConstant(Base constant) { 403 this.constant = constant; 404 } 405 406 public Function getFunction() { 407 return function; 408 } 409 public void setFunction(Function function) { 410 this.function = function; 411 if (parameters == null) 412 parameters = new ArrayList<ExpressionNode>(); 413 } 414 415 public boolean isProximal() { 416 return proximal; 417 } 418 public void setProximal(boolean proximal) { 419 this.proximal = proximal; 420 } 421 public Operation getOperation() { 422 return operation; 423 } 424 public void setOperation(Operation operation) { 425 this.operation = operation; 426 } 427 public ExpressionNode getInner() { 428 return inner; 429 } 430 public void setInner(ExpressionNode value) { 431 this.inner = value; 432 } 433 public ExpressionNode getOpNext() { 434 return opNext; 435 } 436 public void setOpNext(ExpressionNode value) { 437 this.opNext = value; 438 } 439 public List<ExpressionNode> getParameters() { 440 return parameters; 441 } 442 public boolean checkName() { 443 if (!name.startsWith("$")) 444 return true; 445 else 446 return Utilities.existsInList(name, "$this", "$total"); 447 } 448 449 public Kind getKind() { 450 return kind; 451 } 452 453 public void setKind(Kind kind) { 454 this.kind = kind; 455 } 456 457 public ExpressionNode getGroup() { 458 return group; 459 } 460 461 public void setGroup(ExpressionNode group) { 462 this.group = group; 463 } 464 465 public SourceLocation getStart() { 466 return start; 467 } 468 469 public void setStart(SourceLocation start) { 470 this.start = start; 471 } 472 473 public SourceLocation getEnd() { 474 return end; 475 } 476 477 public void setEnd(SourceLocation end) { 478 this.end = end; 479 } 480 481 public SourceLocation getOpStart() { 482 return opStart; 483 } 484 485 public void setOpStart(SourceLocation opStart) { 486 this.opStart = opStart; 487 } 488 489 public SourceLocation getOpEnd() { 490 return opEnd; 491 } 492 493 public void setOpEnd(SourceLocation opEnd) { 494 this.opEnd = opEnd; 495 } 496 497 public String getUniqueId() { 498 return uniqueId; 499 } 500 501 502 public int parameterCount() { 503 if (parameters == null) 504 return 0; 505 else 506 return parameters.size(); 507 } 508 509 public String Canonical() { 510 StringBuilder b = new StringBuilder(); 511 write(b); 512 return b.toString(); 513 } 514 515 public String summary() { 516 switch (kind) { 517 case Name: return uniqueId+": "+name; 518 case Function: return uniqueId+": "+function.toString()+"()"; 519 case Constant: return uniqueId+": "+constant; 520 case Group: return uniqueId+": (Group)"; 521 } 522 return "??"; 523 } 524 525 private void write(StringBuilder b) { 526 527 switch (kind) { 528 case Name: 529 b.append(name); 530 break; 531 case Constant: 532 b.append(constant); 533 break; 534 case Function: 535 b.append(function.toCode()); 536 b.append('('); 537 boolean f = true; 538 for (ExpressionNode n : parameters) { 539 if (f) 540 f = false; 541 else 542 b.append(", "); 543 n.write(b); 544 } 545 b.append(')'); 546 547 break; 548 case Group: 549 b.append('('); 550 group.write(b); 551 b.append(')'); 552 } 553 554 if (inner != null) { 555 b.append('.'); 556 inner.write(b); 557 } 558 if (operation != null) { 559 b.append(' '); 560 b.append(operation.toCode()); 561 b.append(' '); 562 opNext.write(b); 563 } 564 } 565 566 public String check() { 567 568 switch (kind) { 569 case Name: 570 if (Utilities.noString(name)) 571 return "No Name provided @ "+location(); 572 break; 573 574 case Function: 575 if (function == null) 576 return "No Function id provided @ "+location(); 577 for (ExpressionNode n : parameters) { 578 String msg = n.check(); 579 if (msg != null) 580 return msg; 581 } 582 583 break; 584 585 case Unary: 586 break; 587 case Constant: 588 if (constant == null) 589 return "No Constant provided @ "+location(); 590 break; 591 592 case Group: 593 if (group == null) 594 return "No Group provided @ "+location(); 595 else { 596 String msg = group.check(); 597 if (msg != null) 598 return msg; 599 } 600 } 601 if (inner != null) { 602 String msg = inner.check(); 603 if (msg != null) 604 return msg; 605 } 606 if (operation == null) { 607 608 if (opNext != null) 609 return "Next provided when it shouldn't be @ "+location(); 610 } 611 else { 612 if (opNext == null) 613 return "No Next provided @ "+location(); 614 else 615 opNext.check(); 616 } 617 return null; 618 619 } 620 621 private String location() { 622 return Integer.toString(start.getLine())+", "+Integer.toString(start.getColumn()); 623 } 624 625 public TypeDetails getTypes() { 626 return types; 627 } 628 629 public void setTypes(TypeDetails types) { 630 this.types = types; 631 } 632 633 public TypeDetails getOpTypes() { 634 return opTypes; 635 } 636 637 public void setOpTypes(TypeDetails opTypes) { 638 this.opTypes = opTypes; 639 } 640 641}