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