001package org.hl7.fhir.dstu2016may.utils; 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 032 033 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038import java.util.UUID; 039 040import org.hl7.fhir.dstu2016may.model.Base; 041import org.hl7.fhir.dstu2016may.model.BooleanType; 042import org.hl7.fhir.dstu2016may.model.CodeType; 043import org.hl7.fhir.dstu2016may.model.Coding; 044import org.hl7.fhir.dstu2016may.model.ConceptMap; 045import org.hl7.fhir.dstu2016may.model.ConceptMap.SourceElementComponent; 046import org.hl7.fhir.dstu2016may.model.ConceptMap.TargetElementComponent; 047import org.hl7.fhir.dstu2016may.model.DecimalType; 048import org.hl7.fhir.dstu2016may.model.Enumeration; 049import org.hl7.fhir.dstu2016may.model.Enumerations.ConceptMapEquivalence; 050import org.hl7.fhir.dstu2016may.model.ExpressionNode; 051import org.hl7.fhir.dstu2016may.model.IdType; 052import org.hl7.fhir.dstu2016may.model.IntegerType; 053import org.hl7.fhir.dstu2016may.model.Resource; 054import org.hl7.fhir.dstu2016may.model.ResourceFactory; 055import org.hl7.fhir.dstu2016may.model.StringType; 056import org.hl7.fhir.dstu2016may.model.StructureMap; 057import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupComponent; 058import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupInputComponent; 059import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupRuleComponent; 060import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupRuleDependentComponent; 061import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupRuleSourceComponent; 062import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupRuleTargetComponent; 063import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; 064import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapInputMode; 065import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapListMode; 066import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapModelMode; 067import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapStructureComponent; 068import org.hl7.fhir.dstu2016may.model.StructureMap.StructureMapTransform; 069import org.hl7.fhir.dstu2016may.model.Type; 070import org.hl7.fhir.dstu2016may.model.UriType; 071import org.hl7.fhir.dstu2016may.utils.FHIRLexer.FHIRLexerException; 072import org.hl7.fhir.exceptions.FHIRException; 073import org.hl7.fhir.utilities.Utilities; 074 075public class StructureMapUtilities { 076 077 public static final String MAP_WHERE_CHECK = "map.where.check"; 078 public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; 079 public static final String MAP_EXPRESSION = "map.transform.expression"; 080 081 public interface ITransformerServices { 082 // public boolean validateByValueSet(Coding code, String valuesetId); 083 public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; 084 // public Coding translate(Coding code) 085 // ValueSet validation operation 086 // Translation operation 087 // Lookup another tree of data 088 // Create an instance tree 089 // Return the correct string format to refer to a tree (input or output) 090 091 } 092 093 private IWorkerContext worker; 094 private FHIRPathEngine fpe; 095 private Map<String, StructureMap> library; 096 private ITransformerServices services; 097 098 public StructureMapUtilities(IWorkerContext worker, Map<String, StructureMap> library, ITransformerServices services) { 099 super(); 100 this.worker = worker; 101 this.library = library; 102 this.services = services; 103 fpe = new FHIRPathEngine(worker); 104 } 105 106 107 public String render(StructureMap map) throws FHIRException { 108 StringBuilder b = new StringBuilder(); 109 b.append("map \""); 110 b.append(map.getUrl()); 111 b.append("\" = \""); 112 b.append(Utilities.escapeJava(map.getName())); 113 b.append("\"\r\n\r\n"); 114 115 renderUses(b, map); 116 renderImports(b, map); 117 for (StructureMapGroupComponent g : map.getGroup()) 118 renderGroup(b, g); 119 return b.toString(); 120 } 121 122 private void renderUses(StringBuilder b, StructureMap map) { 123 for (StructureMapStructureComponent s : map.getStructure()) { 124 b.append("uses \""); 125 b.append(s.getUrl()); 126 b.append("\" as "); 127 b.append(s.getMode().toCode()); 128 b.append("\r\n"); 129 renderDoco(b, s.getDocumentation()); 130 } 131 if (map.hasStructure()) 132 b.append("\r\n"); 133 } 134 135 private void renderImports(StringBuilder b, StructureMap map) { 136 for (UriType s : map.getImport()) { 137 b.append("imports \""); 138 b.append(s.getValue()); 139 b.append("\"\r\n"); 140 } 141 if (map.hasImport()) 142 b.append("\r\n"); 143 } 144 145 private void renderGroup(StringBuilder b, StructureMapGroupComponent g) throws FHIRException { 146 b.append("group "); 147 b.append(g.getName()); 148 if (g.hasExtends()) { 149 b.append(" extends "); 150 b.append(g.getExtends()); 151 } 152 if (g.hasDocumentation()) 153 renderDoco(b, g.getDocumentation()); 154 b.append("\r\n"); 155 for (StructureMapGroupInputComponent gi : g.getInput()) { 156 b.append(" input "); 157 b.append(gi.getName()); 158 if (gi.hasType()) { 159 b.append(" : "); 160 b.append(gi.getType()); 161 } 162 b.append(" as "); 163 b.append(gi.getMode().toCode()); 164 b.append(";\r\n"); 165 } 166 if (g.hasInput()) 167 b.append("\r\n"); 168 for (StructureMapGroupRuleComponent r : g.getRule()) { 169 renderRule(b, r, 2); 170 } 171 b.append("\r\nendgroup\r\n"); 172 } 173 174 private void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) throws FHIRException { 175 for (int i = 0; i < indent; i++) 176 b.append(' '); 177 b.append(r.getName()); 178 b.append(": for "); 179 boolean first = true; 180 for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { 181 if (first) 182 first = false; 183 else 184 b.append(", "); 185 renderSource(b, rs); 186 } 187 if (r.getTarget().size() > 1) { 188 b.append(" make "); 189 first = true; 190 for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { 191 if (first) 192 first = false; 193 else 194 b.append(", "); 195 b.append("\r\n"); 196 for (int i = 0; i < indent+4; i++) 197 b.append(' '); 198 renderTarget(b, rt); 199 } 200 } else if (r.hasTarget()) { 201 b.append(" make "); 202 renderTarget(b, r.getTarget().get(0)); 203 } 204 if (r.hasRule()) { 205 b.append(" then {"); 206 renderDoco(b, r.getDocumentation()); 207 for (int i = 0; i < indent; i++) 208 b.append(' '); 209 b.append("}\r\n"); 210 } else { 211 if (r.hasDependent()) { 212 first = true; 213 for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { 214 if (first) 215 first = false; 216 else 217 b.append(", "); 218 b.append(rd.getName()); 219 b.append("("); 220 boolean ifirst = true; 221 for (StringType rdp : rd.getVariable()) { 222 if (ifirst) 223 ifirst = false; 224 else 225 b.append(", "); 226 b.append(rd.getVariable()); 227 } 228 } 229 } 230 renderDoco(b, r.getDocumentation()); 231 b.append("\r\n"); 232 } 233 234 } 235 236 private void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs) { 237 if (!rs.getRequired()) 238 b.append("optional "); 239 b.append(rs.getContext()); 240 if (rs.hasElement()) { 241 b.append('.'); 242 b.append(rs.getElement()); 243 } 244 if (rs.hasListMode()) { 245 b.append(" "); 246 if (rs.getListMode() == StructureMapListMode.SHARE) 247 b.append("only_one"); 248 else 249 b.append(rs.getListMode().toCode()); 250 } 251 if (rs.hasVariable()) { 252 b.append(" as "); 253 b.append(rs.getVariable()); 254 } 255 if (rs.hasCondition()) { 256 b.append(" where "); 257 b.append(rs.getCondition()); 258 } 259 if (rs.hasCheck()) { 260 b.append(" check "); 261 b.append(rs.getCheck()); 262 } 263 } 264 265 private void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt) throws FHIRException { 266 b.append(rt.getContext()); 267 if (rt.hasElement()) { 268 b.append('.'); 269 b.append(rt.getElement()); 270 } 271 if (rt.hasTransform()) { 272 b.append(" = "); 273 if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { 274 renderTransformParam(b, rt.getParameter().get(0)); 275 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { 276 b.append(rt.getTransform().toCode()); 277 b.append("("); 278 b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); 279 b.append(((StringType) rt.getParameter().get(1).getValue()).asStringValue()); 280 b.append(")"); 281 } else { 282 b.append(rt.getTransform().toCode()); 283 b.append("("); 284 boolean first = true; 285 for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { 286 if (first) 287 first = false; 288 else 289 b.append(", "); 290 renderTransformParam(b, rtp); 291 } 292 b.append(")"); 293 } 294 } 295 if (rt.hasVariable()) { 296 b.append(" as "); 297 b.append(rt.getVariable()); 298 } 299 for (Enumeration<StructureMapListMode> lm : rt.getListMode()) { 300 b.append(" "); 301 b.append(lm.getValue().toCode()); 302 if (lm.getValue() == StructureMapListMode.SHARE) { 303 b.append(" "); 304 b.append(rt.getListRuleId()); 305 } 306 } 307 } 308 309 private void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) throws FHIRException { 310 if (rtp.hasValueBooleanType()) 311 b.append(rtp.getValueBooleanType().asStringValue()); 312 else if (rtp.hasValueDecimalType()) 313 b.append(rtp.getValueDecimalType().asStringValue()); 314 else if (rtp.hasValueIdType()) 315 b.append(rtp.getValueIdType().asStringValue()); 316 else if (rtp.hasValueDecimalType()) 317 b.append(rtp.getValueDecimalType().asStringValue()); 318 else if (rtp.hasValueIntegerType()) 319 b.append(rtp.getValueIntegerType().asStringValue()); 320 else 321 b.append(Utilities.escapeJava(rtp.getValueStringType().asStringValue())); 322 } 323 324 private void renderDoco(StringBuilder b, String doco) { 325 if (Utilities.noString(doco)) 326 return; 327 b.append(" // "); 328 b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); 329 } 330 331 public StructureMap parse(String text) throws FHIRException { 332 FHIRLexer lexer = new FHIRLexer(text); 333 if (lexer.done()) 334 throw lexer.error("Map Input cannot be empty"); 335 lexer.skipComments(); 336 lexer.token("map"); 337 StructureMap result = new StructureMap(); 338 result.setUrl(lexer.readConstant("url")); 339 result.setId(tail(result.getUrl())); 340 lexer.token("="); 341 result.setName(lexer.readConstant("name")); 342 lexer.skipComments(); 343 344 while (lexer.hasToken("conceptmap")) 345 parseConceptMap(result, lexer); 346 347 while (lexer.hasToken("uses")) 348 parseUses(result, lexer); 349 while (lexer.hasToken("imports")) 350 parseImports(result, lexer); 351 352 parseGroup(result, lexer); 353 354 while (!lexer.done()) { 355 parseGroup(result, lexer); 356 } 357 358 359 return result; 360 } 361 362 private String tail(String url) { 363 return url.substring(url.lastIndexOf("/")+1); 364 } 365 366 private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { 367 lexer.token("conceptmap"); 368 ConceptMap map = new ConceptMap(); 369 String id = lexer.readConstant("map id"); 370 if (!id.startsWith("#")) 371 lexer.error("Concept Map identifier must start with #"); 372 map.setId(id.substring(1)); 373 result.getContained().add(map); 374 lexer.token("{"); 375 lexer.skipComments(); 376 // lexer.token("source"); 377 // map.setSource(new UriType(lexer.readConstant("source"))); 378 // lexer.token("target"); 379 // map.setSource(new UriType(lexer.readConstant("target"))); 380 Map<String, String> prefixes = new HashMap<String, String>(); 381 while (lexer.hasToken("prefix")) { 382 lexer.token("prefix"); 383 String n = lexer.take(); 384 lexer.token("="); 385 String v = lexer.readConstant("prefix url"); 386 prefixes.put(n, v); 387 } 388 while (!lexer.hasToken("}")) { 389 SourceElementComponent e = map.addElement(); 390 e.setSystem(readPrefix(prefixes, lexer)); 391 lexer.token(":"); 392 e.setCode(lexer.take()); 393 TargetElementComponent tgt = e.addTarget(); 394 tgt.setEquivalence(readEquivalence(lexer)); 395 if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) { 396 tgt.setSystem(readPrefix(prefixes, lexer)); 397 lexer.token(":"); 398 tgt.setCode(lexer.take()); 399 } 400 if (lexer.hasComment()) 401 tgt.setComments(lexer.take().substring(2).trim()); 402 } 403 lexer.token("}"); 404 } 405 406 407 private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException { 408 String prefix = lexer.take(); 409 if (!prefixes.containsKey(prefix)) 410 throw lexer.error("Unknown prefix '"+prefix+"'"); 411 return prefixes.get(prefix); 412 } 413 414 415 private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException { 416 String token = lexer.take(); 417 if (token.equals("=")) 418 return ConceptMapEquivalence.EQUAL; 419 if (token.equals("==")) 420 return ConceptMapEquivalence.EQUIVALENT; 421 if (token.equals("!=")) 422 return ConceptMapEquivalence.DISJOINT; 423 if (token.equals("--")) 424 return ConceptMapEquivalence.UNMATCHED; 425 if (token.equals("<=")) 426 return ConceptMapEquivalence.WIDER; 427 if (token.equals("<-")) 428 return ConceptMapEquivalence.SUBSUMES; 429 if (token.equals(">=")) 430 return ConceptMapEquivalence.NARROWER; 431 if (token.equals(">-")) 432 return ConceptMapEquivalence.SPECIALIZES; 433 if (token.equals("~")) 434 return ConceptMapEquivalence.INEXACT; 435 throw lexer.error("Unknown equivalence token '"+token+"'"); 436 } 437 438 439 private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { 440 lexer.token("uses"); 441 StructureMapStructureComponent st = result.addStructure(); 442 st.setUrl(lexer.readConstant("url")); 443 lexer.token("as"); 444 st.setMode(StructureMapModelMode.fromCode(lexer.take())); 445 lexer.skipToken(";"); 446 if (lexer.hasComment()) { 447 st.setDocumentation(lexer.take().substring(2).trim()); 448 } 449 lexer.skipComments(); 450 } 451 452 private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { 453 lexer.token("imports"); 454 result.addImport(lexer.readConstant("url")); 455 lexer.skipToken(";"); 456 if (lexer.hasComment()) { 457 lexer.next(); 458 } 459 lexer.skipComments(); 460 } 461 462 private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { 463 lexer.token("group"); 464 StructureMapGroupComponent group = result.addGroup(); 465 group.setName(lexer.take()); 466 if (lexer.hasToken("extends")) { 467 lexer.next(); 468 group.setExtends(lexer.take()); 469 } 470 lexer.skipComments(); 471 while (lexer.hasToken("input")) 472 parseInput(group, lexer); 473 while (!lexer.hasToken("endgroup")) { 474 if (lexer.done()) 475 throw lexer.error("premature termination expecting 'endgroup'"); 476 parseRule(group.getRule(), lexer); 477 } 478 lexer.next(); 479 lexer.skipComments(); 480 } 481 482 private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException { 483 lexer.token("input"); 484 StructureMapGroupInputComponent input = group.addInput(); 485 input.setName(lexer.take()); 486 if (lexer.hasToken(":")) { 487 lexer.token(":"); 488 input.setType(lexer.take()); 489 } 490 lexer.token("as"); 491 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 492 if (lexer.hasComment()) { 493 input.setDocumentation(lexer.take().substring(2).trim()); 494 } 495 lexer.skipToken(";"); 496 lexer.skipComments(); 497 } 498 499 private void parseRule(List<StructureMapGroupRuleComponent> list, FHIRLexer lexer) throws FHIRException { 500 StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 501 list.add(rule); 502 rule.setName(lexer.takeDottedToken()); 503 lexer.token(":"); 504 lexer.token("for"); 505 boolean done = false; 506 while (!done) { 507 parseSource(rule, lexer); 508 done = !lexer.hasToken(","); 509 if (!done) 510 lexer.next(); 511 } 512 if (lexer.hasToken("make")) { 513 lexer.token("make"); 514 done = false; 515 while (!done) { 516 parseTarget(rule, lexer); 517 done = !lexer.hasToken(","); 518 if (!done) 519 lexer.next(); 520 } 521 } 522 if (lexer.hasToken("then")) { 523 lexer.token("then"); 524 if (lexer.hasToken("{")) { 525 lexer.token("{"); 526 if (lexer.hasComment()) { 527 rule.setDocumentation(lexer.take().substring(2).trim()); 528 } 529 lexer.skipComments(); 530 while (!lexer.hasToken("}")) { 531 if (lexer.done()) 532 throw lexer.error("premature termination expecting '}' in nested group"); 533 parseRule(rule.getRule(), lexer); 534 } 535 lexer.token("}"); 536 } else { 537 done = false; 538 while (!done) { 539 parseRuleReference(rule, lexer); 540 done = !lexer.hasToken(","); 541 if (!done) 542 lexer.next(); 543 } 544 } 545 } else if (lexer.hasComment()) { 546 rule.setDocumentation(lexer.take().substring(2).trim()); 547 } 548 lexer.skipComments(); 549 } 550 551 private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { 552 StructureMapGroupRuleDependentComponent ref = rule.addDependent(); 553 ref.setName(lexer.take()); 554 lexer.token("("); 555 boolean done = false; 556 while (!done) { 557 ref.addVariable(lexer.take()); 558 done = !lexer.hasToken(","); 559 if (!done) 560 lexer.next(); 561 } 562 lexer.token(")"); 563 } 564 565 private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 566 StructureMapGroupRuleSourceComponent source = rule.addSource(); 567 if (lexer.hasToken("optional")) 568 lexer.next(); 569 else 570 source.setRequired(true); 571 source.setContext(lexer.take()); 572 if (lexer.hasToken(".")) { 573 lexer.token("."); 574 source.setElement(lexer.take()); 575 } 576 if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "only_one")) 577 if (lexer.getCurrent().equals("only_one")) { 578 source.setListMode(StructureMapListMode.SHARE); 579 lexer.take(); 580 } else 581 source.setListMode(StructureMapListMode.fromCode(lexer.take())); 582 583 if (lexer.hasToken("as")) { 584 lexer.take(); 585 source.setVariable(lexer.take()); 586 } 587 if (lexer.hasToken("where")) { 588 lexer.take(); 589 ExpressionNode node = fpe.parse(lexer); 590 source.setUserData(MAP_WHERE_EXPRESSION, node); 591 source.setCondition(node.toString()); 592 } 593 if (lexer.hasToken("check")) { 594 lexer.take(); 595 ExpressionNode node = fpe.parse(lexer); 596 source.setUserData(MAP_WHERE_CHECK, node); 597 source.setCheck(node.toString()); 598 } 599 } 600 601 private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 602 StructureMapGroupRuleTargetComponent target = rule.addTarget(); 603 target.setContext(lexer.take()); 604 if (lexer.hasToken(".")) { 605 lexer.token("."); 606 target.setElement(lexer.take()); 607 } 608 if (lexer.hasToken("=")) { 609 lexer.token("="); 610 boolean isConstant = lexer.isConstant(true); 611 String name = lexer.take(); 612 if (lexer.hasToken("(")) { 613 target.setTransform(StructureMapTransform.fromCode(name)); 614 lexer.token("("); 615 if (target.getTransform() == StructureMapTransform.EVALUATE) { 616 parseParameter(target, lexer); 617 lexer.token(","); 618 ExpressionNode node = fpe.parse(lexer); 619 target.setUserData(MAP_EXPRESSION, node); 620 target.addParameter().setValue(new StringType(node.toString())); 621 } else { 622 while (!lexer.hasToken(")")) { 623 parseParameter(target, lexer); 624 if (!lexer.hasToken(")")) 625 lexer.token(","); 626 } 627 } 628 lexer.token(")"); 629 } else { 630 target.setTransform(StructureMapTransform.COPY); 631 if (!isConstant) { 632 String id = name; 633 while (lexer.hasToken(".")) { 634 id = id + lexer.take() + lexer.take(); 635 } 636 target.addParameter().setValue(new IdType(id)); 637 } 638 else 639 target.addParameter().setValue(readConstant(name, lexer)); 640 } 641 } 642 if (lexer.hasToken("as")) { 643 lexer.take(); 644 target.setVariable(lexer.take()); 645 } 646 while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "only_one")) { 647 if (lexer.getCurrent().equals("share")) { 648 target.addListMode(StructureMapListMode.SHARE); 649 lexer.next(); 650 target.setListRuleId(lexer.take()); 651 } else if (lexer.getCurrent().equals("first")) 652 target.addListMode(StructureMapListMode.FIRST); 653 else 654 target.addListMode(StructureMapListMode.LAST); 655 lexer.next(); 656 } 657 } 658 659 660 private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException { 661 if (!lexer.isConstant(true)) { 662 target.addParameter().setValue(new IdType(lexer.take())); 663 } else if (lexer.isStringConstant()) 664 target.addParameter().setValue(new StringType(lexer.readConstant("??"))); 665 else { 666 target.addParameter().setValue(readConstant(lexer.take(), lexer)); 667 } 668 } 669 670 private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 671 if (Utilities.isInteger(s)) 672 return new IntegerType(s); 673 else if (Utilities.isDecimal(s, false)) 674 return new DecimalType(s); 675 else if (Utilities.existsInList(s, "true", "false")) 676 return new BooleanType(s.equals("true")); 677 else 678 return new StringType(lexer.processConstant(s)); 679 } 680 681 682 public enum VariableMode { 683 INPUT, OUTPUT 684 } 685 686 public class Variable { 687 private VariableMode mode; 688 private String name; 689 private Base object; 690 public Variable(VariableMode mode, String name, Base object) { 691 super(); 692 this.mode = mode; 693 this.name = name; 694 this.object = object; 695 } 696 public VariableMode getMode() { 697 return mode; 698 } 699 public String getName() { 700 return name; 701 } 702 public Base getObject() { 703 return object; 704 } 705 706 } 707 708 public class Variables { 709 private List<Variable> list = new ArrayList<Variable>(); 710 711 public void add(VariableMode mode, String name, Base object) { 712 Variable vv = null; 713 for (Variable v : list) 714 if ((v.mode == mode) && v.getName().equals(name)) 715 vv = v; 716 if (vv != null) 717 list.remove(vv); 718 list.add(new Variable(mode, name, object)); 719 } 720 721 public Variables copy() { 722 Variables result = new Variables(); 723 result.list.addAll(list); 724 return result; 725 } 726 727 public Base get(VariableMode mode, String name) { 728 for (Variable v : list) 729 if ((v.mode == mode) && v.getName().equals(name)) 730 return v.getObject(); 731 return null; 732 } 733 } 734 735 public class TransformContext { 736 private Object appInfo; 737 738 public TransformContext(Object appInfo) { 739 super(); 740 this.appInfo = appInfo; 741 } 742 743 public Object getAppInfo() { 744 return appInfo; 745 } 746 747 } 748 749 private void log(String cnt) { 750 System.out.println(cnt); 751 } 752 753 /** 754 * Given an item, return all the children that conform to the pattern described in name 755 * 756 * Possible patterns: 757 * - a simple name (which may be the base of a name with [] e.g. value[x]) 758 * - a name with a type replacement e.g. valueCodeableConcept 759 * - * which means all children 760 * - ** which means all descendents 761 * 762 * @param item 763 * @param name 764 * @param result 765 * @throws FHIRException 766 */ 767 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 768 for (Base v : item.listChildrenByName(name, true)) 769 if (v != null) 770 result.add(v); 771 } 772 773 public Base transform(Base source, StructureMap map) { 774 return null; 775 } 776 777 public void transform(Object appInfo, Base source, StructureMap map, Base target) throws Exception { 778 TransformContext context = new TransformContext(appInfo); 779 Variables vars = new Variables(); 780 vars.add(VariableMode.INPUT, "src", source); 781 vars.add(VariableMode.OUTPUT, "tgt", target); 782 783 executeGroup("", context, map, vars, map.getGroup().get(0)); 784 } 785 786 private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group) throws Exception { 787 log(indent+"Group : "+group.getName()); 788 // todo: extends 789 // todo: check inputs 790 for (StructureMapGroupRuleComponent r : group.getRule()) { 791 executeRule(indent+" ", context, map, vars, group, r); 792 } 793 } 794 795 private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule) throws Exception { 796 log(indent+"rule : "+rule.getName()); 797 Variables srcVars = vars.copy(); 798 if (rule.getSource().size() != 1) 799 throw new Exception("not handled yet"); 800 List<Variables> source = analyseSource(context, srcVars, rule.getSource().get(0)); 801 if (source != null) { 802 for (Variables v : source) { 803 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 804 processTarget(context, v, map, t); 805 } 806 if (rule.hasRule()) { 807 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 808 executeRule(indent +" ", context, map, v, group, childrule); 809 } 810 } else if (rule.hasDependent()) { 811 for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 812 executeDependency(indent+" ", context, map, v, group, dependent); 813 } 814 } 815 } 816 } 817 } 818 819 private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws Exception { 820 StructureMap targetMap = null; 821 StructureMapGroupComponent target = null; 822 for (StructureMapGroupComponent grp : map.getGroup()) { 823 if (grp.getName().equals(dependent.getName())) { 824 if (targetMap == null) { 825 targetMap = map; 826 target = grp; 827 } else 828 throw new FHIRException("Multiple possible matches for rule '"+dependent.getName()+"'"); 829 } 830 } 831 832 for (UriType imp : map.getImport()) { 833 StructureMap impMap = library.get(imp.getValue()); 834 if (impMap == null) 835 throw new FHIRException("Unable to find map "+imp.getValue()+" (Known Maps = "+Utilities.listCanonicalUrls(library.keySet())+")"); 836 for (StructureMapGroupComponent grp : impMap.getGroup()) { 837 if (grp.getName().equals(dependent.getName())) { 838 if (targetMap == null) { 839 targetMap = impMap; 840 target = grp; 841 } else 842 throw new FHIRException("Multiple possible matches for rule '"+dependent.getName()+"'"); 843 } 844 } 845 } 846 if (target == null) 847 throw new FHIRException("No matches found for rule '"+dependent.getName()+"'"); 848 849 if (target.getInput().size() != dependent.getVariable().size()) { 850 throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); 851 } 852 Variables v = new Variables(); 853 for (int i = 0; i < target.getInput().size(); i++) { 854 StructureMapGroupInputComponent input = target.getInput().get(i); 855 StringType var = dependent.getVariable().get(i); 856 VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; 857 Base vv = vin.get(mode, var.getValue()); 858 if (vv == null) 859 throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' has no value"); 860 v.add(mode, input.getName(), vv); 861 } 862 executeGroup(indent+" ", context, targetMap, v, target); 863 } 864 865 866 private List<Variables> analyseSource(TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src) throws Exception { 867 Base b = vars.get(VariableMode.INPUT, src.getContext()); 868 if (b == null) 869 throw new FHIRException("Unknown input variable "+src.getContext()); 870 871 if (src.hasCondition()) { 872 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); 873 if (expr == null) { 874 expr = fpe.parse(src.getCondition()); 875 // fpe.check(context.appInfo, ??, ??, expr) 876 src.setUserData(MAP_WHERE_EXPRESSION, expr); 877 } 878 if (!fpe.evaluateToBoolean(null, b, expr)) 879 return null; 880 } 881 882 if (src.hasCheck()) { 883 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); 884 if (expr == null) { 885 expr = fpe.parse(src.getCondition()); 886 // fpe.check(context.appInfo, ??, ??, expr) 887 src.setUserData(MAP_WHERE_CHECK, expr); 888 } 889 if (!fpe.evaluateToBoolean(null, b, expr)) 890 throw new Exception("Check condition failed"); 891 } 892 893 List<Base> items = new ArrayList<Base>(); 894 if (!src.hasElement()) 895 items.add(b); 896 else 897 getChildrenByName(b, src.getElement(), items); 898 List<Variables> result = new ArrayList<Variables>(); 899 for (Base r : items) { 900 Variables v = vars.copy(); 901 if (src.hasVariable()) 902 v.add(VariableMode.INPUT, src.getVariable(), r); 903 result.add(v); 904 } 905 return result; 906 } 907 908 909 private void processTarget(TransformContext context, Variables vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt) throws Exception { 910 Base dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); 911 if (dest == null) 912 throw new Exception("target context not known: "+tgt.getContext()); 913 if (!tgt.hasElement()) 914 throw new Exception("Not supported yet"); 915 Base v = null; 916 if (tgt.hasTransform()) { 917 v = runTransform(context, map, tgt, vars); 918 if (v != null) 919 dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); 920 } else 921 v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); 922 if (tgt.hasVariable() && v != null) 923 vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); 924 } 925 926 private Base runTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, Variables vars) throws FHIRException { 927 switch (tgt.getTransform()) { 928 case CREATE : 929 return ResourceFactory.createResourceOrType(getParamString(vars, tgt.getParameter().get(0))); 930 case COPY : 931 return getParam(vars, tgt.getParameter().get(0)); 932 case EVALUATE : 933 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 934 if (expr == null) { 935 expr = fpe.parse(getParamString(vars, tgt.getParameter().get(1))); 936 tgt.setUserData(MAP_WHERE_EXPRESSION, expr); 937 } 938 List<Base> v = fpe.evaluate(null, null, getParam(vars, tgt.getParameter().get(0)), expr); 939 if (v.size() != 1) 940 throw new FHIRException("evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); 941 return v.get(0); 942 943 case TRUNCATE : 944 String src = getParamString(vars, tgt.getParameter().get(0)); 945 String len = getParamString(vars, tgt.getParameter().get(1)); 946 if (Utilities.isInteger(len)) { 947 int l = Integer.parseInt(len); 948 if (src.length() > l) 949 src = src.substring(0, l); 950 } 951 return new StringType(src); 952 case ESCAPE : 953 throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 954 case CAST : 955 throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 956 case APPEND : 957 throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 958 case TRANSLATE : 959 return translate(context, map, vars, tgt.getParameter()); 960 case REFERENCE : 961 throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 962 case DATEOP : 963 throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 964 case UUID : 965 return new IdType(UUID.randomUUID().toString()); 966 case POINTER : 967 Base b = getParam(vars, tgt.getParameter().get(0)); 968 if (b instanceof Resource) 969 return new UriType("urn:uuid:"+((Resource) b).getId()); 970 else 971 throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); 972 default: 973 throw new Error("Transform Unknown: "+tgt.getTransform().toCode()); 974 } 975 } 976 977 978 private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) { 979 Base b = getParam(vars, parameter); 980 if (b == null || !b.hasPrimitiveValue()) 981 return null; 982 return b.primitiveValue(); 983 } 984 985 986 private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) { 987 Type p = parameter.getValue(); 988 if (!(p instanceof IdType)) 989 return p; 990 else { 991 Base b = vars.get(VariableMode.INPUT, ((IdType) p).asStringValue()); 992 if (b == null) 993 b = vars.get(VariableMode.OUTPUT, ((IdType) p).asStringValue()); 994 return b; 995 } 996 } 997 998 999 private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException { 1000 Base src = getParam(vars, parameter.get(0)); 1001 String id = getParamString(vars, parameter.get(1)); 1002 String fld = getParamString(vars, parameter.get(2)); 1003 return translate(context, map, src, id, fld); 1004 } 1005 1006 public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { 1007 Coding src = new Coding(); 1008 if (source.isPrimitive()) { 1009 src.setCode(source.primitiveValue()); 1010 } else if ("Coding".equals(source.fhirType())) { 1011 Base[] b = source.getProperty("system".hashCode(), "system", true); 1012 if (b.length == 1) 1013 src.setSystem(b[0].primitiveValue()); 1014 b = source.getProperty("code".hashCode(), "code", true); 1015 if (b.length == 1) 1016 src.setCode(b[0].primitiveValue()); 1017 } else if ("CE".equals(source.fhirType())) { 1018 Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); 1019 if (b.length == 1) 1020 src.setSystem(b[0].primitiveValue()); 1021 b = source.getProperty("code".hashCode(), "code", true); 1022 if (b.length == 1) 1023 src.setCode(b[0].primitiveValue()); 1024 } else 1025 throw new FHIRException("Unable to translate source "+source.fhirType()); 1026 1027 if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { 1028 String uri = worker.oid2Uri(src.getCode()); 1029 if (uri == null) 1030 uri = "urn:oid:"+src.getCode(); 1031 if ("uri".equals(fieldToReturn)) 1032 return new UriType(uri); 1033 else 1034 throw new FHIRException("Error in return code"); 1035 } else { 1036 ConceptMap cmap = null; 1037 if (conceptMapUrl.startsWith("#")) { 1038 for (Resource r : map.getContained()) { 1039 if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) 1040 cmap = (ConceptMap) r; 1041 } 1042 } else 1043 cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); 1044 Coding outcome = null; 1045 boolean done = false; 1046 String message = null; 1047 if (cmap == null) { 1048 if (services == null) 1049 message = "No map found for "+conceptMapUrl; 1050 else { 1051 outcome = services.translate(context.appInfo, src, conceptMapUrl); 1052 done = true; 1053 } 1054 } else { 1055 List<SourceElementComponent> list = new ArrayList<SourceElementComponent>(); 1056 for (SourceElementComponent e : cmap.getElement()) { 1057 if (!src.hasSystem() && src.getCode().equals(e.getCode())) 1058 list.add(e); 1059 else if (src.hasSystem() && src.getSystem().equals(e.getSystem()) && src.getCode().equals(e.getCode())) 1060 list.add(e); 1061 } 1062 if (list.size() == 0) 1063 done = true; 1064 else if (list.get(0).getTarget().size() == 0) 1065 message = "Concept map "+conceptMapUrl+" found no translation for "+src.getCode(); 1066 else { 1067 for (TargetElementComponent tgt : list.get(0).getTarget()) { 1068 if (tgt.getEquivalence() == ConceptMapEquivalence.EQUAL || tgt.getEquivalence() == ConceptMapEquivalence.EQUIVALENT || tgt.getEquivalence() == ConceptMapEquivalence.WIDER) { 1069 if (done) { 1070 message = "Concept map "+conceptMapUrl+" found multiple matches for "+src.getCode(); 1071 done = false; 1072 } else { 1073 done = true; 1074 outcome = new Coding().setCode(tgt.getCode()).setSystem(tgt.getSystem()); 1075 } 1076 } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) { 1077 done = true; 1078 } 1079 } 1080 if (!done) 1081 message = "Concept map "+conceptMapUrl+" found no usable translation for "+src.getCode(); 1082 } 1083 } 1084 if (!done) 1085 throw new FHIRException(message); 1086 if (outcome == null) 1087 return null; 1088 if ("code".equals(fieldToReturn)) 1089 return new CodeType(outcome.getCode()); 1090 else 1091 return outcome; 1092 } 1093 } 1094 1095 1096 public Map<String, StructureMap> getLibrary() { 1097 return library; 1098 } 1099 1100}