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