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