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