001package org.hl7.fhir.dstu2.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 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.HashSet; 035import java.util.List; 036import java.util.Set; 037 038import org.hl7.fhir.dstu2.utils.IWorkerContext; 039import org.hl7.fhir.utilities.Utilities; 040 041public class ExpressionNode { 042 043 public enum Kind { 044 Name, Function, Constant, Group 045 } 046 047 public static class SourceLocation { 048 private int line; 049 private int column; 050 051 public SourceLocation(int line, int column) { 052 super(); 053 this.line = line; 054 this.column = column; 055 } 056 057 public int getLine() { 058 return line; 059 } 060 061 public int getColumn() { 062 return column; 063 } 064 065 public void setLine(int line) { 066 this.line = line; 067 } 068 069 public void setColumn(int column) { 070 this.column = column; 071 } 072 073 public String toString() { 074 return Integer.toString(line) + ", " + Integer.toString(column); 075 } 076 } 077 078 public enum Function { 079 Custom, 080 081 Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, 082 Item /* implicit from name[] */, As, Is, Single, First, Last, Tail, Skip, Take, Iif, ToInteger, ToDecimal, ToString, 083 Substring, StartsWith, EndsWith, Matches, ReplaceMatches, Contains, Replace, Length, Children, Descendants, 084 MemberOf, Trace, Today, Now, Resolve, Extension; 085 086 public static Function fromCode(String name) { 087 if (name.equals("empty")) 088 return Function.Empty; 089 if (name.equals("not")) 090 return Function.Not; 091 if (name.equals("exists")) 092 return Function.Exists; 093 if (name.equals("subsetOf")) 094 return Function.SubsetOf; 095 if (name.equals("supersetOf")) 096 return Function.SupersetOf; 097 if (name.equals("isDistinct")) 098 return Function.IsDistinct; 099 if (name.equals("distinct")) 100 return Function.Distinct; 101 if (name.equals("count")) 102 return Function.Count; 103 if (name.equals("where")) 104 return Function.Where; 105 if (name.equals("select")) 106 return Function.Select; 107 if (name.equals("all")) 108 return Function.All; 109 if (name.equals("repeat")) 110 return Function.Repeat; 111 if (name.equals("item")) 112 return Function.Item; 113 if (name.equals("as")) 114 return Function.As; 115 if (name.equals("is")) 116 return Function.Is; 117 if (name.equals("single")) 118 return Function.Single; 119 if (name.equals("first")) 120 return Function.First; 121 if (name.equals("last")) 122 return Function.Last; 123 if (name.equals("tail")) 124 return Function.Tail; 125 if (name.equals("skip")) 126 return Function.Skip; 127 if (name.equals("take")) 128 return Function.Take; 129 if (name.equals("iif")) 130 return Function.Iif; 131 if (name.equals("toInteger")) 132 return Function.ToInteger; 133 if (name.equals("toDecimal")) 134 return Function.ToDecimal; 135 if (name.equals("toString")) 136 return Function.ToString; 137 if (name.equals("substring")) 138 return Function.Substring; 139 if (name.equals("startsWith")) 140 return Function.StartsWith; 141 if (name.equals("endsWith")) 142 return Function.EndsWith; 143 if (name.equals("matches")) 144 return Function.Matches; 145 if (name.equals("replaceMatches")) 146 return Function.ReplaceMatches; 147 if (name.equals("contains")) 148 return Function.Contains; 149 if (name.equals("replace")) 150 return Function.Replace; 151 if (name.equals("length")) 152 return Function.Length; 153 if (name.equals("children")) 154 return Function.Children; 155 if (name.equals("descendants")) 156 return Function.Descendants; 157 if (name.equals("memberOf")) 158 return Function.MemberOf; 159 if (name.equals("trace")) 160 return Function.Trace; 161 if (name.equals("today")) 162 return Function.Today; 163 if (name.equals("now")) 164 return Function.Now; 165 if (name.equals("resolve")) 166 return Function.Resolve; 167 if (name.equals("extension")) 168 return Function.Extension; 169 return null; 170 } 171 172 public String toCode() { 173 switch (this) { 174 case Empty: 175 return "empty"; 176 case Not: 177 return "not"; 178 case Exists: 179 return "exists"; 180 case SubsetOf: 181 return "subsetOf"; 182 case SupersetOf: 183 return "supersetOf"; 184 case IsDistinct: 185 return "isDistinct"; 186 case Distinct: 187 return "distinct"; 188 case Count: 189 return "count"; 190 case Where: 191 return "where"; 192 case Select: 193 return "select"; 194 case All: 195 return "all"; 196 case Repeat: 197 return "repeat"; 198 case Item: 199 return "item"; 200 case As: 201 return "as"; 202 case Is: 203 return "is"; 204 case Single: 205 return "single"; 206 case First: 207 return "first"; 208 case Last: 209 return "last"; 210 case Tail: 211 return "tail"; 212 case Skip: 213 return "skip"; 214 case Take: 215 return "take"; 216 case Iif: 217 return "iif"; 218 case ToInteger: 219 return "toInteger"; 220 case ToDecimal: 221 return "toDecimal"; 222 case ToString: 223 return "toString"; 224 case Substring: 225 return "substring"; 226 case StartsWith: 227 return "startsWith"; 228 case EndsWith: 229 return "endsWith"; 230 case Matches: 231 return "matches"; 232 case ReplaceMatches: 233 return "replaceMatches"; 234 case Contains: 235 return "contains"; 236 case Replace: 237 return "replace"; 238 case Length: 239 return "length"; 240 case Children: 241 return "children"; 242 case Descendants: 243 return "descendants"; 244 case MemberOf: 245 return "memberOf"; 246 case Trace: 247 return "trace"; 248 case Today: 249 return "today"; 250 case Now: 251 return "now"; 252 case Resolve: 253 return "resolve"; 254 case Extension: 255 return "extension"; 256 default: 257 return "??"; 258 } 259 } 260 } 261 262 public enum Operation { 263 Equals, Equivalent, NotEquals, NotEquivalent, LessThen, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, 264 And, Xor, Implies, Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains; 265 266 public static Operation fromCode(String name) { 267 if (Utilities.noString(name)) 268 return null; 269 if (name.equals("=")) 270 return Operation.Equals; 271 if (name.equals("~")) 272 return Operation.Equivalent; 273 if (name.equals("!=")) 274 return Operation.NotEquals; 275 if (name.equals("!~")) 276 return Operation.NotEquivalent; 277 if (name.equals(">")) 278 return Operation.Greater; 279 if (name.equals("<")) 280 return Operation.LessThen; 281 if (name.equals(">=")) 282 return Operation.GreaterOrEqual; 283 if (name.equals("<=")) 284 return Operation.LessOrEqual; 285 if (name.equals("|")) 286 return Operation.Union; 287 if (name.equals("or")) 288 return Operation.Or; 289 if (name.equals("and")) 290 return Operation.And; 291 if (name.equals("xor")) 292 return Operation.Xor; 293 if (name.equals("is")) 294 return Operation.Is; 295 if (name.equals("as")) 296 return Operation.As; 297 if (name.equals("*")) 298 return Operation.Times; 299 if (name.equals("/")) 300 return Operation.DivideBy; 301 if (name.equals("+")) 302 return Operation.Plus; 303 if (name.equals("-")) 304 return Operation.Minus; 305 if (name.equals("&")) 306 return Operation.Concatenate; 307 if (name.equals("implies")) 308 return Operation.Implies; 309 if (name.equals("div")) 310 return Operation.Div; 311 if (name.equals("mod")) 312 return Operation.Mod; 313 if (name.equals("in")) 314 return Operation.In; 315 if (name.equals("contains")) 316 return Operation.Contains; 317 return null; 318 319 } 320 321 public String toCode() { 322 switch (this) { 323 case Equals: 324 return "="; 325 case Equivalent: 326 return "~"; 327 case NotEquals: 328 return "!="; 329 case NotEquivalent: 330 return "!~"; 331 case Greater: 332 return ">"; 333 case LessThen: 334 return "<"; 335 case GreaterOrEqual: 336 return ">="; 337 case LessOrEqual: 338 return "<="; 339 case Union: 340 return "|"; 341 case Or: 342 return "or"; 343 case And: 344 return "and"; 345 case Xor: 346 return "xor"; 347 case Times: 348 return "*"; 349 case DivideBy: 350 return "/"; 351 case Plus: 352 return "+"; 353 case Minus: 354 return "-"; 355 case Concatenate: 356 return "&"; 357 case Implies: 358 return "implies"; 359 case Is: 360 return "is"; 361 case As: 362 return "as"; 363 case Div: 364 return "div"; 365 case Mod: 366 return "mod"; 367 case In: 368 return "in"; 369 case Contains: 370 return "contains"; 371 default: 372 return "??"; 373 } 374 } 375 } 376 377 public enum CollectionStatus { 378 SINGLETON, ORDERED, UNORDERED 379 } 380 381 public static class TypeDetails { 382 @Override 383 public String toString() { 384 return (collectionStatus == null ? "" : collectionStatus.toString()) + (types == null ? "[]" : types.toString()); 385 } 386 387 private Set<String> types = new HashSet<String>(); 388 private CollectionStatus collectionStatus; 389 390 public TypeDetails(CollectionStatus collectionStatus, String... names) { 391 super(); 392 this.collectionStatus = collectionStatus; 393 for (String n : names) 394 this.types.add(n); 395 } 396 397 public TypeDetails(CollectionStatus collectionStatus, Set<String> names) { 398 super(); 399 this.collectionStatus = collectionStatus; 400 for (String n : names) 401 this.types.add(n); 402 } 403 404 public void addType(String n) { 405 this.types.add(n); 406 } 407 408 public void addTypes(Collection<String> n) { 409 this.types.addAll(n); 410 } 411 412 public boolean hasType(IWorkerContext context, String... tn) { 413 for (String t : tn) 414 if (types.contains(t)) 415 return true; 416 for (String t : tn) { 417 StructureDefinition sd = context.fetchTypeDefinition(t); 418 while (sd != null) { 419 if (types.contains(sd.getId())) 420 return true; 421 if (sd.hasBase()) 422 sd = context.fetchResource(StructureDefinition.class, sd.getBase()); 423 else 424 sd = null; 425 } 426 } 427 return false; 428 } 429 430 public void update(TypeDetails source) { 431 types.addAll(source.types); 432 if (collectionStatus == null) 433 collectionStatus = source.collectionStatus; 434 else if (source.collectionStatus == CollectionStatus.UNORDERED) 435 collectionStatus = source.collectionStatus; 436 else 437 collectionStatus = CollectionStatus.ORDERED; 438 } 439 440 public TypeDetails union(TypeDetails right) { 441 TypeDetails result = new TypeDetails(null); 442 if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) 443 result.collectionStatus = CollectionStatus.UNORDERED; 444 else 445 result.collectionStatus = CollectionStatus.ORDERED; 446 result.types.addAll(types); 447 result.types.addAll(right.types); 448 return result; 449 } 450 451 public boolean hasNoTypes() { 452 return types.isEmpty(); 453 } 454 455 public Set<String> getTypes() { 456 return types; 457 } 458 459 public TypeDetails toSingleton() { 460 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 461 result.types.addAll(types); 462 return result; 463 } 464 465 public CollectionStatus getCollectionStatus() { 466 return collectionStatus; 467 } 468 469 public boolean hasType(Set<String> tn) { 470 for (String t : tn) 471 if (types.contains(t)) 472 return true; 473 return false; 474 } 475 476 public String describe() { 477 return types.toString(); 478 } 479 480 public String getType() { 481 for (String t : types) 482 return t; 483 return null; 484 } 485 486 } 487 488 // the expression will have one of either name or constant 489 private String uniqueId; 490 private Kind kind; 491 private String name; 492 private String constant; 493 private Function function; 494 private List<ExpressionNode> parameters; // will be created if there is a function 495 private ExpressionNode inner; 496 private ExpressionNode group; 497 private Operation operation; 498 private boolean proximal; // a proximal operation is the first in the sequence of operations. This is 499 // significant when evaluating the outcomes 500 private ExpressionNode opNext; 501 private SourceLocation start; 502 private SourceLocation end; 503 private SourceLocation opStart; 504 private SourceLocation opEnd; 505 private TypeDetails types; 506 private TypeDetails opTypes; 507 508 public ExpressionNode(int uniqueId) { 509 super(); 510 this.uniqueId = Integer.toString(uniqueId); 511 } 512 513 public String toString() { 514 StringBuilder b = new StringBuilder(); 515 switch (kind) { 516 case Name: 517 b.append(name); 518 break; 519 case Function: 520 if (function == Function.Item) 521 b.append("["); 522 else { 523 b.append(name); 524 b.append("("); 525 } 526 boolean first = true; 527 for (ExpressionNode n : parameters) { 528 if (first) 529 first = false; 530 else 531 b.append(", "); 532 b.append(n.toString()); 533 } 534 if (function == Function.Item) 535 b.append("]"); 536 else { 537 b.append(")"); 538 } 539 break; 540 case Constant: 541 b.append(Utilities.escapeJava(constant)); 542 break; 543 case Group: 544 b.append("("); 545 b.append(group.toString()); 546 b.append(")"); 547 } 548 if (inner != null) { 549 b.append("."); 550 b.append(inner.toString()); 551 } 552 if (operation != null) { 553 b.append(" "); 554 b.append(operation.toCode()); 555 b.append(" "); 556 b.append(opNext.toString()); 557 } 558 559 return b.toString(); 560 } 561 562 public String getName() { 563 return name; 564 } 565 566 public void setName(String name) { 567 this.name = name; 568 } 569 570 public String getConstant() { 571 return constant; 572 } 573 574 public void setConstant(String constant) { 575 this.constant = constant; 576 } 577 578 public Function getFunction() { 579 return function; 580 } 581 582 public void setFunction(Function function) { 583 this.function = function; 584 if (parameters == null) 585 parameters = new ArrayList<ExpressionNode>(); 586 } 587 588 public boolean isProximal() { 589 return proximal; 590 } 591 592 public void setProximal(boolean proximal) { 593 this.proximal = proximal; 594 } 595 596 public Operation getOperation() { 597 return operation; 598 } 599 600 public void setOperation(Operation operation) { 601 this.operation = operation; 602 } 603 604 public ExpressionNode getInner() { 605 return inner; 606 } 607 608 public void setInner(ExpressionNode value) { 609 this.inner = value; 610 } 611 612 public ExpressionNode getOpNext() { 613 return opNext; 614 } 615 616 public void setOpNext(ExpressionNode value) { 617 this.opNext = value; 618 } 619 620 public List<ExpressionNode> getParameters() { 621 return parameters; 622 } 623 624 public boolean checkName() { 625 if (!name.startsWith("$")) 626 return true; 627 else 628 return name.equals("$this"); 629 } 630 631 public Kind getKind() { 632 return kind; 633 } 634 635 public void setKind(Kind kind) { 636 this.kind = kind; 637 } 638 639 public ExpressionNode getGroup() { 640 return group; 641 } 642 643 public void setGroup(ExpressionNode group) { 644 this.group = group; 645 } 646 647 public SourceLocation getStart() { 648 return start; 649 } 650 651 public void setStart(SourceLocation start) { 652 this.start = start; 653 } 654 655 public SourceLocation getEnd() { 656 return end; 657 } 658 659 public void setEnd(SourceLocation end) { 660 this.end = end; 661 } 662 663 public SourceLocation getOpStart() { 664 return opStart; 665 } 666 667 public void setOpStart(SourceLocation opStart) { 668 this.opStart = opStart; 669 } 670 671 public SourceLocation getOpEnd() { 672 return opEnd; 673 } 674 675 public void setOpEnd(SourceLocation opEnd) { 676 this.opEnd = opEnd; 677 } 678 679 public String getUniqueId() { 680 return uniqueId; 681 } 682 683 public int parameterCount() { 684 if (parameters == null) 685 return 0; 686 else 687 return parameters.size(); 688 } 689 690 public String Canonical() { 691 StringBuilder b = new StringBuilder(); 692 write(b); 693 return b.toString(); 694 } 695 696 public String summary() { 697 switch (kind) { 698 case Name: 699 return uniqueId + ": " + name; 700 case Function: 701 return uniqueId + ": " + function.toString() + "()"; 702 case Constant: 703 return uniqueId + ": " + constant; 704 case Group: 705 return uniqueId + ": (Group)"; 706 } 707 return "??"; 708 } 709 710 private void write(StringBuilder b) { 711 712 switch (kind) { 713 case Name: 714 b.append(name); 715 break; 716 case Constant: 717 b.append(constant); 718 break; 719 case Function: 720 b.append(function.toCode()); 721 b.append('('); 722 boolean f = true; 723 for (ExpressionNode n : parameters) { 724 if (f) 725 f = false; 726 else 727 b.append(", "); 728 n.write(b); 729 } 730 b.append(')'); 731 732 break; 733 case Group: 734 b.append('('); 735 group.write(b); 736 b.append(')'); 737 } 738 739 if (inner != null) { 740 b.append('.'); 741 inner.write(b); 742 } 743 if (operation != null) { 744 b.append(' '); 745 b.append(operation.toCode()); 746 b.append(' '); 747 opNext.write(b); 748 } 749 } 750 751 public String check() { 752 753 switch (kind) { 754 case Name: 755 if (Utilities.noString(name)) 756 return "No Name provided @ " + location(); 757 break; 758 759 case Function: 760 if (function == null) 761 return "No Function id provided @ " + location(); 762 for (ExpressionNode n : parameters) { 763 String msg = n.check(); 764 if (msg != null) 765 return msg; 766 } 767 768 break; 769 770 case Constant: 771 if (Utilities.noString(constant)) 772 return "No Constant provided @ " + location(); 773 break; 774 775 case Group: 776 if (group == null) 777 return "No Group provided @ " + location(); 778 else { 779 String msg = group.check(); 780 if (msg != null) 781 return msg; 782 } 783 } 784 if (inner != null) { 785 String msg = inner.check(); 786 if (msg != null) 787 return msg; 788 } 789 if (operation == null) { 790 791 if (opNext != null) 792 return "Next provided when it shouldn't be @ " + location(); 793 } else { 794 if (opNext == null) 795 return "No Next provided @ " + location(); 796 else 797 opNext.check(); 798 } 799 return null; 800 801 } 802 803 private String location() { 804 return Integer.toString(start.getLine()) + ", " + Integer.toString(start.getColumn()); 805 } 806 807 public TypeDetails getTypes() { 808 return types; 809 } 810 811 public void setTypes(TypeDetails types) { 812 this.types = types; 813 } 814 815 public TypeDetails getOpTypes() { 816 return opTypes; 817 } 818 819 public void setOpTypes(TypeDetails opTypes) { 820 this.opTypes = opTypes; 821 } 822 823}