
001package org.hl7.fhir.r5.utils.structuremap; 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// remember group resolution 034// trace - account for which wasn't transformed in the source 035 036import org.hl7.fhir.exceptions.DefinitionException; 037import org.hl7.fhir.exceptions.FHIRException; 038import org.hl7.fhir.exceptions.FHIRFormatError; 039import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; 040import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 041import org.hl7.fhir.r5.context.ContextUtilities; 042import org.hl7.fhir.r5.context.IWorkerContext; 043import org.hl7.fhir.r5.elementmodel.Element; 044import org.hl7.fhir.r5.elementmodel.Manager; 045import org.hl7.fhir.r5.elementmodel.Property; 046import org.hl7.fhir.r5.fhirpath.ExpressionNode; 047import org.hl7.fhir.r5.fhirpath.FHIRLexer; 048import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 049import org.hl7.fhir.r5.fhirpath.TypeDetails; 050import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; 051import org.hl7.fhir.r5.fhirpath.FHIRLexer.FHIRLexerException; 052import org.hl7.fhir.r5.fhirpath.TypeDetails.ProfiledType; 053import org.hl7.fhir.r5.model.*; 054import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; 055import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode; 056import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; 057import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; 058import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 059import org.hl7.fhir.r5.model.Enumeration; 060import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 061import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; 062import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 063import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 064import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 065import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 066import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 067import org.hl7.fhir.r5.model.StructureMap.*; 068import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 069import org.hl7.fhir.r5.renderers.TerminologyRenderer; 070import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 071import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 072import org.hl7.fhir.r5.utils.ToolingExtensions; 073import org.hl7.fhir.r5.utils.UserDataNames; 074import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 075import org.hl7.fhir.utilities.FhirPublication; 076import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 077import org.hl7.fhir.utilities.Utilities; 078import org.hl7.fhir.utilities.validation.ValidationOptions; 079import org.hl7.fhir.utilities.xhtml.NodeType; 080import org.hl7.fhir.utilities.xhtml.XhtmlNode; 081 082import java.io.IOException; 083import java.util.*; 084 085/** 086 * Services in this class: 087 * <p> 088 * string render(map) - take a structure and convert it to text 089 * map parse(text) - take a text representation and parse it 090 * getTargetType(map) - return the definition for the type to create to hand in 091 * transform(appInfo, source, map, target) - transform from source to target following the map 092 * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform 093 * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings 094 * 095 * @author Grahame Grieve 096 */ 097@MarkedToMoveToAdjunctPackage 098public class StructureMapUtilities { 099 100 public static final String MAP_WHERE_CHECK = "map.where.check"; 101 public static final String MAP_WHERE_LOG = "map.where.log"; 102 public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; 103 public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; 104 public static final String MAP_EXPRESSION = "map.transform.expression"; 105 private static final boolean MULTIPLE_TARGETS_ONELINE = true; 106 public static final String AUTO_VAR_NAME = "vvv"; 107 public static final String DEF_GROUP_NAME = "DefaultMappingGroupAnonymousAlias"; 108 109 private final IWorkerContext worker; 110 private final FHIRPathEngine fpe; 111 private ITransformerServices services; 112 private ProfileKnowledgeProvider pkp; 113 private final Map<String, Integer> ids = new HashMap<String, Integer>(); 114 private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5); 115 private final ProfileUtilities profileUtilities; 116 private boolean exceptionsForChecks = true; 117 private boolean debug; 118 119 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) { 120 super(); 121 this.worker = worker; 122 this.services = services; 123 this.pkp = pkp; 124 fpe = new FHIRPathEngine(worker); 125 fpe.setHostServices(new FHIRPathHostServices(this)); 126 profileUtilities = new ProfileUtilities(worker, null, null); 127 } 128 129 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { 130 super(); 131 this.worker = worker; 132 this.services = services; 133 fpe = new FHIRPathEngine(worker); 134 fpe.setHostServices(new FHIRPathHostServices(this)); 135 profileUtilities = new ProfileUtilities(worker, null, null); 136 } 137 138 public StructureMapUtilities(IWorkerContext worker) { 139 super(); 140 this.worker = worker; 141 fpe = new FHIRPathEngine(worker); 142 fpe.setHostServices(new FHIRPathHostServices(this)); 143 profileUtilities = new ProfileUtilities(worker, null, null); 144 145 } 146 147 public static String render(StructureMap map) { 148 StringBuilder b = new StringBuilder(); 149 b.append("/// url = \""+map.getUrl()+"\"\r\n"); 150 b.append("/// name = \""+map.getName()+"\"\r\n"); 151 b.append("/// title = \""+map.getTitle()+"\"\r\n"); 152 b.append("/// status = \""+map.getStatus().toCode()+"\"\r\n"); 153 b.append("\r\n"); 154 if (map.getDescription() != null) { 155 renderMultilineDoco(b, map.getDescription(), 0); 156 b.append("\r\n"); 157 } 158 renderConceptMaps(b, map); 159 renderUses(b, map); 160 renderImports(b, map); 161 for (StructureMapGroupComponent g : map.getGroup()) 162 renderGroup(b, g); 163 return b.toString(); 164 } 165 166 private static void renderConceptMaps(StringBuilder b, StructureMap map) { 167 for (Resource r : map.getContained()) { 168 if (r instanceof ConceptMap) { 169 produceConceptMap(b, (ConceptMap) r); 170 } 171 } 172 } 173 174 private static void produceConceptMap(StringBuilder b, ConceptMap cm) { 175 b.append("conceptmap \""); 176 b.append(cm.getId()); 177 b.append("\" {\r\n"); 178 Map<String, String> prefixesSrc = new HashMap<String, String>(); 179 Map<String, String> prefixesTgt = new HashMap<String, String>(); 180 char prefix = 's'; 181 for (ConceptMapGroupComponent cg : cm.getGroup()) { 182 if (!prefixesSrc.containsKey(cg.getSource())) { 183 prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); 184 b.append(" prefix "); 185 b.append(prefix); 186 b.append(" = \""); 187 b.append(cg.getSource()); 188 b.append("\"\r\n"); 189 prefix++; 190 } 191 if (!prefixesTgt.containsKey(cg.getTarget())) { 192 prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); 193 b.append(" prefix "); 194 b.append(prefix); 195 b.append(" = \""); 196 b.append(cg.getTarget()); 197 b.append("\"\r\n"); 198 prefix++; 199 } 200 } 201 b.append("\r\n"); 202 for (ConceptMapGroupComponent cg : cm.getGroup()) { 203 if (cg.hasUnmapped()) { 204 b.append(" unmapped for "); 205 b.append(prefixesSrc.get(cg.getSource())); 206 b.append(" = "); 207 b.append(cg.getUnmapped().getMode().toCode()); 208 b.append("\r\n"); 209 } 210 } 211 212 for (ConceptMapGroupComponent cg : cm.getGroup()) { 213 for (SourceElementComponent ce : cg.getElement()) { 214 b.append(" "); 215 b.append(prefixesSrc.get(cg.getSource())); 216 b.append(":"); 217 if (Utilities.isToken(ce.getCode())) { 218 b.append(ce.getCode()); 219 } else { 220 b.append("\""); 221 b.append(ce.getCode()); 222 b.append("\""); 223 } 224 b.append(" "); 225 b.append(getChar(ce.getTargetFirstRep().getRelationship())); 226 b.append(" "); 227 b.append(prefixesTgt.get(cg.getTarget())); 228 b.append(":"); 229 if (Utilities.isToken(ce.getTargetFirstRep().getCode())) { 230 b.append(ce.getTargetFirstRep().getCode()); 231 } else { 232 b.append("\""); 233 b.append(ce.getTargetFirstRep().getCode()); 234 b.append("\""); 235 } 236 b.append("\r\n"); 237 } 238 } 239 b.append("}\r\n\r\n"); 240 } 241 242 private static Object getChar(ConceptMapRelationship relationship) { 243 switch (relationship) { 244 case RELATEDTO: 245 return "-"; 246 case EQUIVALENT: 247 return "=="; 248 case NOTRELATEDTO: 249 return "!="; 250 case SOURCEISNARROWERTHANTARGET: 251 return "<="; 252 case SOURCEISBROADERTHANTARGET: 253 return ">="; 254 default: 255 return "??"; 256 } 257 } 258 259 private static void renderUses(StringBuilder b, StructureMap map) { 260 for (StructureMapStructureComponent s : map.getStructure()) { 261 b.append("uses \""); 262 b.append(s.getUrl()); 263 b.append("\" "); 264 if (s.hasAlias()) { 265 b.append("alias "); 266 b.append(s.getAlias()); 267 b.append(" "); 268 } 269 b.append("as "); 270 b.append(s.getMode().toCode()); 271 renderDoco(b, s.getDocumentation()); 272 b.append("\r\n"); 273 } 274 if (map.hasStructure()) 275 b.append("\r\n"); 276 } 277 278 private static void renderImports(StringBuilder b, StructureMap map) { 279 for (UriType s : map.getImport()) { 280 b.append("imports \""); 281 b.append(s.getValue()); 282 b.append("\"\r\n"); 283 } 284 if (map.hasImport()) 285 b.append("\r\n"); 286 } 287 288 public static String groupToString(StructureMapGroupComponent g) { 289 StringBuilder b = new StringBuilder(); 290 renderGroup(b, g); 291 return b.toString(); 292 } 293 294 private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { 295 if (g.hasDocumentation()) { 296 renderMultilineDoco(b, g.getDocumentation(), 0); 297 } 298 b.append("group "); 299 b.append(g.getName()); 300 b.append("("); 301 boolean first = true; 302 for (StructureMapGroupInputComponent gi : g.getInput()) { 303 if (first) 304 first = false; 305 else 306 b.append(", "); 307 b.append(gi.getMode().toCode()); 308 b.append(" "); 309 b.append(gi.getName()); 310 if (gi.hasType()) { 311 b.append(" : "); 312 b.append(gi.getType()); 313 } 314 } 315 b.append(")"); 316 if (g.hasExtends()) { 317 b.append(" extends "); 318 b.append(g.getExtends()); 319 } 320 321 if (g.hasTypeMode()) { 322 switch (g.getTypeMode()) { 323 case TYPES: 324 b.append(" <<types>>"); 325 break; 326 case TYPEANDTYPES: 327 b.append(" <<type+>>"); 328 break; 329 default: // NONE, NULL 330 } 331 } 332 b.append(" {\r\n"); 333 for (StructureMapGroupRuleComponent r : g.getRule()) { 334 renderRule(b, r, 2); 335 } 336 b.append("}\r\n\r\n"); 337 } 338 339 public static String ruleToString(StructureMapGroupRuleComponent r) { 340 StringBuilder b = new StringBuilder(); 341 renderRule(b, r, 0); 342 return b.toString(); 343 } 344 345 private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { 346 if (r.hasFormatCommentPre()) { 347 renderMultilineDoco(b, r.getFormatCommentsPre(), indent); 348 } 349 for (int i = 0; i < indent; i++) 350 b.append(' '); 351 boolean canBeAbbreviated = checkisSimple(r); 352 { 353 boolean first = true; 354 for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { 355 if (first) 356 first = false; 357 else 358 b.append(", "); 359 renderSource(b, rs, canBeAbbreviated); 360 } 361 } 362 if (r.getTarget().size() > 1) { 363 b.append(" -> "); 364 boolean first = true; 365 for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { 366 if (first) 367 first = false; 368 else 369 b.append(", "); 370 if (MULTIPLE_TARGETS_ONELINE) 371 b.append(' '); 372 else { 373 b.append("\r\n"); 374 for (int i = 0; i < indent + 4; i++) 375 b.append(' '); 376 } 377 renderTarget(b, rt, false); 378 } 379 } else if (r.hasTarget()) { 380 b.append(" -> "); 381 renderTarget(b, r.getTarget().get(0), canBeAbbreviated); 382 } 383 if (r.hasRule()) { 384 b.append(" then {\r\n"); 385 for (StructureMapGroupRuleComponent ir : r.getRule()) { 386 renderRule(b, ir, indent + 2); 387 } 388 for (int i = 0; i < indent; i++) 389 b.append(' '); 390 b.append("}"); 391 } else if (!canBeAbbreviated) { 392 if (r.hasDependent()) { 393 b.append(" then "); 394 boolean first = true; 395 for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { 396 if (first) 397 first = false; 398 else 399 b.append(", "); 400 b.append(rd.getName()); 401 b.append("("); 402 boolean ifirst = true; 403 for (StructureMapGroupRuleTargetParameterComponent rdp : rd.getParameter()) { 404 if (ifirst) 405 ifirst = false; 406 else 407 b.append(", "); 408 renderTransformParam(b, rdp); 409 } 410 b.append(")"); 411 } 412 } 413 } 414 if (r.hasName()) { 415 String n = ntail(r.getName()); 416 if (!n.startsWith("\"")) 417 n = "\"" + n + "\""; 418 if (!matchesName(n, r.getSource())) { 419 b.append(" "); 420 b.append(n); 421 } 422 } 423 b.append(";"); 424 b.append("\r\n"); 425 } 426 427 private static boolean matchesName(String n, List<StructureMapGroupRuleSourceComponent> source) { 428 if (source.size() != 1) 429 return false; 430 if (!source.get(0).hasElement()) 431 return false; 432 String s = source.get(0).getElement(); 433 if (n.equals(s) || n.equals("\"" + s + "\"")) 434 return true; 435 if (source.get(0).hasType()) { 436 s = source.get(0).getElement() + "-" + source.get(0).getType(); 437 return n.equals(s) || n.equals("\"" + s + "\""); 438 } 439 return false; 440 } 441 442 private static String ntail(String name) { 443 if (name == null) 444 return null; 445 if (name.startsWith("\"")) { 446 name = name.substring(1); 447 name = name.substring(0, name.length() - 1); 448 } 449 return "\"" + (name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : name) + "\""; 450 } 451 452 private static boolean checkisSimple(StructureMapGroupRuleComponent r) { 453 return 454 (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && 455 (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && 456 (r.getDependent().size() == 0 || (r.getDependent().size() == 1 && StructureMapUtilities.DEF_GROUP_NAME.equals(r.getDependentFirstRep().getName()))) && (r.getRule().size() == 0); 457 } 458 459 public static String sourceToString(StructureMapGroupRuleSourceComponent r) { 460 StringBuilder b = new StringBuilder(); 461 renderSource(b, r, false); 462 return b.toString(); 463 } 464 465 private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { 466 b.append(rs.getContext()); 467 if (rs.getContext().equals("@search")) { 468 b.append('('); 469 b.append(rs.getElement()); 470 b.append(')'); 471 } else if (rs.hasElement()) { 472 b.append('.'); 473 b.append(rs.getElement()); 474 } 475 if (rs.hasType()) { 476 b.append(" : "); 477 b.append(rs.getType()); 478 if (rs.hasMin()) { 479 b.append(" "); 480 b.append(rs.getMin()); 481 b.append(".."); 482 b.append(rs.getMax()); 483 } 484 } 485 486 if (rs.hasListMode()) { 487 b.append(" "); 488 b.append(rs.getListMode().toCode()); 489 } 490 if (rs.hasDefaultValue()) { 491 b.append(" default "); 492 b.append("\"" + Utilities.escapeJson(rs.getDefaultValue()) + "\""); 493 } 494 if (!abbreviate && rs.hasVariable()) { 495 b.append(" as "); 496 b.append(rs.getVariable()); 497 } 498 if (rs.hasCondition()) { 499 b.append(" where "); 500 b.append(rs.getCondition()); 501 } 502 if (rs.hasCheck()) { 503 b.append(" check "); 504 b.append(rs.getCheck()); 505 } 506 if (rs.hasLogMessage()) { 507 b.append(" log "); 508 b.append(rs.getLogMessage()); 509 } 510 } 511 512 public static String targetToString(StructureMapGroupRuleTargetComponent rt) { 513 StringBuilder b = new StringBuilder(); 514 renderTarget(b, rt, false); 515 return b.toString(); 516 } 517 518 private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { 519 if (rt.hasContext()) { 520 b.append(rt.getContext()); 521 if (rt.hasElement()) { 522 b.append('.'); 523 b.append(rt.getElement()); 524 } 525 } 526 if (!abbreviate && rt.hasTransform()) { 527 if (rt.hasContext()) 528 b.append(" = "); 529 if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { 530 renderTransformParam(b, rt.getParameter().get(0)); 531 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { 532 b.append("("); 533 b.append(((StringType) rt.getParameter().get(0).getValue()).asStringValue()); 534 b.append(")"); 535 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { 536 b.append(rt.getTransform().toCode()); 537 b.append("("); 538 b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); 539 b.append(", "); 540 b.append(((StringType) rt.getParameter().get(1).getValue()).asStringValue()); 541 b.append(")"); 542 } else { 543 b.append(rt.getTransform().toCode()); 544 b.append("("); 545 boolean first = true; 546 for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { 547 if (first) 548 first = false; 549 else 550 b.append(", "); 551 renderTransformParam(b, rtp); 552 } 553 b.append(")"); 554 } 555 } 556 if (!abbreviate && rt.hasVariable()) { 557 b.append(" as "); 558 b.append(rt.getVariable()); 559 } 560 for (Enumeration<StructureMapTargetListMode> lm : rt.getListMode()) { 561 b.append(" "); 562 b.append(lm.getValue().toCode()); 563 if (lm.getValue() == StructureMapTargetListMode.SHARE) { 564 b.append(" "); 565 b.append(rt.getListRuleId()); 566 } 567 } 568 } 569 570 public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { 571 StringBuilder b = new StringBuilder(); 572 renderTransformParam(b, rtp); 573 return b.toString(); 574 } 575 576 private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { 577 try { 578 if (rtp.hasValueBooleanType()) 579 b.append(rtp.getValueBooleanType().asStringValue()); 580 else if (rtp.hasValueDecimalType()) 581 b.append(rtp.getValueDecimalType().asStringValue()); 582 else if (rtp.hasValueIdType()) 583 b.append(rtp.getValueIdType().asStringValue()); 584 else if (rtp.hasValueIntegerType()) 585 b.append(rtp.getValueIntegerType().asStringValue()); 586 else 587 b.append("'" + Utilities.escapeJava(rtp.getValueStringType().asStringValue()) + "'"); 588 } catch (FHIRException e) { 589 e.printStackTrace(); 590 b.append("error!"); 591 } 592 } 593 594 private static void renderDoco(StringBuilder b, String doco) { 595 if (Utilities.noString(doco)) 596 return; 597 if (b != null && b.length() > 1 && b.charAt(b.length() - 1) != '\n' && b.charAt(b.length() - 1) != ' ') { 598 b.append(" "); 599 } 600 b.append("// "); 601 b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); 602 } 603 604 private static void renderMultilineDoco(StringBuilder b, String doco, int indent) { 605 if (Utilities.noString(doco)) 606 return; 607 String[] lines = doco.split("\\r?\\n"); 608 for (String line : lines) { 609 for (int i = 0; i < indent; i++) 610 b.append(' '); 611 renderDoco(b, line); 612 b.append("\r\n"); 613 } 614 } 615 private static void renderMultilineDoco(StringBuilder b, List<String> doco, int indent) { 616 if (doco == null || doco.isEmpty()) 617 return; 618 for (String line : doco) { 619 for (int i = 0; i < indent; i++) 620 b.append(' '); 621 renderDoco(b, line); 622 b.append("\r\n"); 623 } 624 } 625 626 public ITransformerServices getServices() { 627 return services; 628 } 629 630 public IWorkerContext getWorker() { 631 return worker; 632 } 633 634 public StructureMap parse(String text, String srcName) throws FHIRException { 635 FHIRLexer lexer = new FHIRLexer(Utilities.stripBOM(text), srcName, true, true); 636 if (lexer.done()) 637 throw lexer.error("Map Input cannot be empty"); 638 StructureMap result = new StructureMap(); 639 if (lexer.hasToken("map")) { 640 lexer.token("map"); 641 result.setUrl(lexer.readConstant("url")); 642 lexer.token("="); 643 result.setName(lexer.readConstant("name")); 644 result.setDescription(lexer.getAllComments()); 645 result.setStatus(PublicationStatus.DRAFT); 646 } 647 while (lexer.hasToken("///")) { 648 lexer.next(); 649 String fid = lexer.takeDottedToken(); 650 lexer.token("="); 651 switch (fid) { 652 case "url" : 653 result.setUrl(lexer.readConstant("url")); 654 break; 655 case "name" : 656 result.setName(lexer.readConstant("name")); 657 break; 658 case "title" : 659 result.setTitle(lexer.readConstant("title")); 660 break; 661 case "description" : 662 result.setTitle(lexer.readConstant("description")); 663 break; 664 case "status" : 665 result.setStatus(PublicationStatus.fromCode(lexer.readConstant("status"))); 666 break; 667 default: 668 lexer.readConstant("nothing"); 669 // nothing 670 } 671 } 672 if (!result.hasId() && result.hasName()) { 673 String id = Utilities.makeId(result.getName()); 674 if (!Utilities.noString(id)) { 675 result.setId(id); 676 } 677 } 678 if (!result.hasStatus()) { 679 result.setStatus(PublicationStatus.DRAFT); 680 } 681 if (!result.hasDescription() && result.hasTitle()) { 682 result.setDescription(result.getTitle()); 683 } 684 685 while (lexer.hasToken("conceptmap")) 686 parseConceptMap(result, lexer); 687 688 while (lexer.hasToken("uses")) 689 parseUses(result, lexer); 690 while (lexer.hasToken("imports")) 691 parseImports(result, lexer); 692 693 while (lexer.hasToken("conceptmap")) 694 parseConceptMap(result, lexer); 695 696 while (!lexer.done()) { 697 parseGroup(result, lexer); 698 } 699 700 return result; 701 } 702 703 704 private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { 705 ConceptMap map = new ConceptMap(); 706 map.addFormatCommentsPre(lexer.getComments()); 707 lexer.token("conceptmap"); 708 String id = lexer.readConstant("map id"); 709 if (id.startsWith("#")) 710 throw lexer.error("Concept Map identifier must start with #"); 711 map.setId(id); 712 map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format 713 result.getContained().add(map); 714 lexer.token("{"); 715 // lexer.token("source"); 716 // map.setSource(new UriType(lexer.readConstant("source"))); 717 // lexer.token("target"); 718 // map.setSource(new UriType(lexer.readConstant("target"))); 719 Map<String, String> prefixes = new HashMap<String, String>(); 720 while (lexer.hasToken("prefix")) { 721 lexer.token("prefix"); 722 String n = lexer.take(); 723 lexer.token("="); 724 String v = lexer.readConstant("prefix url"); 725 prefixes.put(n, v); 726 } 727 while (lexer.hasToken("unmapped")) { 728 List<String> comments = lexer.cloneComments(); 729 lexer.token("unmapped"); 730 lexer.token("for"); 731 String n = readPrefix(prefixes, lexer); 732 ConceptMapGroupComponent g = getGroup(map, n, null); 733 g.addFormatCommentsPre(comments); 734 lexer.token("="); 735 String v = lexer.take(); 736 if (v.equals("provided")) { 737 g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.USESOURCECODE); 738 } else 739 throw lexer.error("Only unmapped mode PROVIDED is supported at this time"); 740 } 741 while (!lexer.hasToken("}")) { 742 List<String> comments = lexer.cloneComments(); 743 String srcs = readPrefix(prefixes, lexer); 744 lexer.token(":"); 745 String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); 746 ConceptMapRelationship rel = readRelationship(lexer); 747 String tgts = readPrefix(prefixes, lexer); 748 ConceptMapGroupComponent g = getGroup(map, srcs, tgts); 749 SourceElementComponent e = g.addElement(); 750 e.addFormatCommentsPre(comments); 751 e.setCode(sc); 752 if (e.getCode().startsWith("\"")) { 753 e.setCode(lexer.processConstant(e.getCode())); 754 } 755 TargetElementComponent tgt = e.addTarget(); 756 tgt.setRelationship(rel); 757 lexer.token(":"); 758 tgt.setCode(lexer.take()); 759 if (tgt.getCode().startsWith("\"")) { 760 tgt.setCode(lexer.processConstant(tgt.getCode())); 761 } 762 // tgt.setComment(lexer.getAllComments()); 763 } 764 map.addFormatCommentsPost(lexer.getComments()); 765 lexer.token("}"); 766 } 767 768 769 770 private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { 771 for (ConceptMapGroupComponent grp : map.getGroup()) { 772 if (grp.getSource().equals(srcs)) 773 if (!grp.hasTarget() || tgts == null || tgts.equals(grp.getTarget())) { 774 if (!grp.hasTarget() && tgts != null) 775 grp.setTarget(tgts); 776 return grp; 777 } 778 } 779 ConceptMapGroupComponent grp = map.addGroup(); 780 grp.setSource(srcs); 781 grp.setTarget(tgts); 782 return grp; 783 } 784 785 786 787 private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException { 788 String prefix = lexer.take(); 789 if (!prefixes.containsKey(prefix)) 790 throw lexer.error("Unknown prefix '" + prefix + "'"); 791 return prefixes.get(prefix); 792 } 793 794 795 private ConceptMapRelationship readRelationship(FHIRLexer lexer) throws FHIRLexerException { 796 String token = lexer.take(); 797 if (token.equals("-")) 798 return ConceptMapRelationship.RELATEDTO; 799 if (token.equals("==")) 800 return ConceptMapRelationship.EQUIVALENT; 801 if (token.equals("!=")) 802 return ConceptMapRelationship.NOTRELATEDTO; 803 if (token.equals("<=")) 804 return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET; 805 if (token.equals(">=")) 806 return ConceptMapRelationship.SOURCEISBROADERTHANTARGET; 807 throw lexer.error("Unknown relationship token '" + token + "'"); 808 } 809 810 811 private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { 812 lexer.token("uses"); 813 StructureMapStructureComponent st = result.addStructure(); 814 st.setUrl(lexer.readConstant("url")); 815 if (lexer.hasToken("alias")) { 816 lexer.token("alias"); 817 st.setAlias(lexer.take()); 818 } 819 lexer.token("as"); 820 String doco; 821 if (lexer.getCurrent().equals("source")) { 822 st.setMode(StructureMapModelMode.SOURCE); 823 doco = lexer.tokenWithTrailingComment("source"); 824 } else if (lexer.getCurrent().equals("target")) { 825 st.setMode(StructureMapModelMode.TARGET); 826 doco = lexer.tokenWithTrailingComment("target"); 827 } else { 828 throw lexer.error("Found '"+lexer.getCurrent()+"' expecting 'source' or 'target'"); 829 } 830 if (lexer.hasToken(";")) { 831 doco = lexer.tokenWithTrailingComment(";"); 832 } 833 st.setDocumentation(doco); 834 } 835 836 837 838 private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { 839 lexer.token("imports"); 840 result.addImport(lexer.readConstant("url")); 841 lexer.skipToken(";"); 842 } 843 844 private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { 845 String comment = lexer.getAllComments(); 846 lexer.token("group"); 847 StructureMapGroupComponent group = result.addGroup(); 848 if (comment != null) { 849 group.setDocumentation(comment); 850 } 851 boolean newFmt = false; 852 if (lexer.hasToken("for")) { 853 lexer.token("for"); 854 if ("type".equals(lexer.getCurrent())) { 855 lexer.token("type"); 856 lexer.token("+"); 857 lexer.token("types"); 858 group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); 859 } else { 860 lexer.token("types"); 861 group.setTypeMode(StructureMapGroupTypeMode.TYPES); 862 } 863 } 864 group.setName(lexer.take()); 865 if (lexer.hasToken("(")) { 866 newFmt = true; 867 lexer.take(); 868 while (!lexer.hasToken(")")) { 869 parseInput(group, lexer, true); 870 if (lexer.hasToken(",")) 871 lexer.token(","); 872 } 873 lexer.take(); 874 } 875 if (lexer.hasToken("extends")) { 876 lexer.next(); 877 group.setExtends(lexer.take()); 878 } 879 if (newFmt) { 880 if (lexer.hasToken("<")) { 881 lexer.token("<"); 882 lexer.token("<"); 883 if (lexer.hasToken("types")) { 884 group.setTypeMode(StructureMapGroupTypeMode.TYPES); 885 lexer.token("types"); 886 } else { 887 lexer.token("type"); 888 lexer.token("+"); 889 group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); 890 } 891 lexer.token(">"); 892 lexer.token(">"); 893 } 894 lexer.token("{"); 895 } 896 if (newFmt) { 897 while (!lexer.hasToken("}")) { 898 if (lexer.done()) 899 throw lexer.error("premature termination expecting 'endgroup'"); 900 parseRule(result, group.getRule(), lexer, true); 901 } 902 } else { 903 while (lexer.hasToken("input")) 904 parseInput(group, lexer, false); 905 while (!lexer.hasToken("endgroup")) { 906 if (lexer.done()) 907 throw lexer.error("premature termination expecting 'endgroup'"); 908 parseRule(result, group.getRule(), lexer, false); 909 } 910 } 911 group.addFormatCommentsPost(lexer.getComments()); 912 lexer.next(); 913 if (newFmt && lexer.hasToken(";")) 914 lexer.next(); 915 } 916 917 918 919 private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException { 920 StructureMapGroupInputComponent input = group.addInput(); 921 if (newFmt) { 922 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 923 } else 924 lexer.token("input"); 925 input.setName(lexer.take()); 926 if (lexer.hasToken(":")) { 927 lexer.token(":"); 928 input.setType(lexer.take()); 929 } 930 if (!newFmt) { 931 lexer.token("as"); 932 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 933 input.setDocumentation(lexer.getAllComments()); 934 lexer.skipToken(";"); 935 } 936 } 937 938 939 940 private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer, boolean newFmt) throws FHIRException { 941 StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 942 if (!newFmt) { 943 rule.setName(lexer.takeDottedToken()); 944 lexer.token(":"); 945 lexer.token("for"); 946 } else { 947 rule.addFormatCommentsPre(lexer.getComments()); 948 } 949 list.add(rule); 950 boolean done = false; 951 while (!done) { 952 parseSource(rule, lexer); 953 done = !lexer.hasToken(","); 954 if (!done) 955 lexer.next(); 956 } 957 if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) { 958 lexer.token(newFmt ? "->" : "make"); 959 done = false; 960 while (!done) { 961 parseTarget(rule, lexer); 962 done = !lexer.hasToken(","); 963 if (!done) 964 lexer.next(); 965 } 966 } 967 if (lexer.hasToken("then")) { 968 lexer.token("then"); 969 if (lexer.hasToken("{")) { 970 lexer.token("{"); 971 while (!lexer.hasToken("}")) { 972 if (lexer.done()) 973 throw lexer.error("premature termination expecting '}' in nested group"); 974 parseRule(map, rule.getRule(), lexer, newFmt); 975 } 976 lexer.token("}"); 977 } else { 978 done = false; 979 while (!done) { 980 parseRuleReference(rule, lexer); 981 done = !lexer.hasToken(","); 982 if (!done) 983 lexer.next(); 984 } 985 } 986 } 987 if (isSimpleSyntax(rule)) { 988 rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); 989 rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); 990 rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created 991 // no dependencies - imply what is to be done based on types 992 } 993 if (newFmt) { 994 if (lexer.isConstant()) { 995 if (lexer.isStringConstant()) { 996 rule.setName(fixName(lexer.readConstant("ruleName"))); 997 } else { 998 rule.setName(lexer.take()); 999 } 1000 } else { 1001 if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement() && exceptionsForChecks ) 1002 throw lexer.error("Complex rules must have an explicit name"); 1003 if (rule.getSourceFirstRep().hasType()) 1004 rule.setName(rule.getSourceFirstRep().getElement() + Utilities.capitalize(rule.getSourceFirstRep().getType())); 1005 else 1006 rule.setName(rule.getSourceFirstRep().getElement()); 1007 } 1008 String doco = lexer.tokenWithTrailingComment(";"); 1009 if (doco != null) { 1010 rule.setDocumentation(doco); 1011 } 1012 } 1013 } 1014 1015 private String fixName(String c) { 1016 return c.replace("-", ""); 1017 } 1018 1019 private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { 1020 return 1021 (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && 1022 (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && 1023 (rule.getDependent().size() == 0 && rule.getRule().size() == 0); 1024 } 1025 1026 1027 1028 private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { 1029 StructureMapGroupRuleDependentComponent ref = rule.addDependent(); 1030 ref.setName(lexer.take()); 1031 lexer.token("("); 1032 boolean done = false; 1033 while (!done) { 1034 parseParameter(ref, lexer); 1035 done = !lexer.hasToken(","); 1036 if (!done) 1037 lexer.next(); 1038 } 1039 lexer.token(")"); 1040 } 1041 1042 1043 private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 1044 StructureMapGroupRuleSourceComponent source = rule.addSource(); 1045 source.setContext(lexer.take()); 1046 if (source.getContext().equals("search") && lexer.hasToken("(")) { 1047 source.setContext("@search"); 1048 lexer.take(); 1049 ExpressionNode node = fpe.parse(lexer); 1050 source.setUserData(MAP_SEARCH_EXPRESSION, node); 1051 source.setElement(node.toString()); 1052 lexer.token(")"); 1053 } else if (lexer.hasToken(".")) { 1054 lexer.token("."); 1055 source.setElement(readAsStringOrProcessedConstant(lexer.take(), lexer)); 1056 } 1057 if (lexer.hasToken(":")) { 1058 // type and cardinality 1059 lexer.token(":"); 1060 source.setType(lexer.takeDottedToken()); 1061 } 1062 if (Utilities.isInteger(lexer.getCurrent())) { 1063 source.setMin(lexer.takeInt()); 1064 lexer.token(".."); 1065 source.setMax(lexer.take()); 1066 } 1067 if (lexer.hasToken("default")) { 1068 lexer.token("default"); 1069 source.setDefaultValue(lexer.readConstant("default value")); 1070 } 1071 if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) 1072 source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); 1073 1074 if (lexer.hasToken("as")) { 1075 lexer.take(); 1076 source.setVariable(lexer.take()); 1077 } 1078 if (lexer.hasToken("where")) { 1079 lexer.take(); 1080 ExpressionNode node = fpe.parse(lexer); 1081 source.setUserData(MAP_WHERE_EXPRESSION, node); 1082 source.setCondition(node.toString()); 1083 } 1084 if (lexer.hasToken("check")) { 1085 lexer.take(); 1086 ExpressionNode node = fpe.parse(lexer); 1087 source.setUserData(MAP_WHERE_CHECK, node); 1088 source.setCheck(node.toString()); 1089 } 1090 if (lexer.hasToken("log")) { 1091 lexer.take(); 1092 ExpressionNode node = fpe.parse(lexer); 1093 source.setUserData(MAP_WHERE_LOG, node); 1094 source.setLogMessage(node.toString()); 1095 } 1096 } 1097 1098 private String readAsStringOrProcessedConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 1099 if (s.startsWith("\"") || s.startsWith("`")) 1100 return lexer.processConstant(s); 1101 else 1102 return s; 1103 } 1104 1105 private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 1106 StructureMapGroupRuleTargetComponent target = rule.addTarget(); 1107 String start = lexer.take(); 1108 if (lexer.hasToken(".")) { 1109 target.setContext(start); 1110 start = null; 1111 lexer.token("."); 1112 target.setElement(lexer.take()); 1113 } 1114 String name; 1115 boolean isConstant = false; 1116 if (lexer.hasToken("=")) { 1117 if (start != null) 1118 target.setContext(start); 1119 lexer.token("="); 1120 isConstant = lexer.isConstant(); 1121 name = lexer.take(); 1122 } else 1123 name = start; 1124 1125 if ("(".equals(name)) { 1126 // inline fluentpath expression 1127 target.setTransform(StructureMapTransform.EVALUATE); 1128 ExpressionNode node = fpe.parse(lexer); 1129 target.setUserData(MAP_EXPRESSION, node); 1130 target.addParameter().setValue(new StringType(node.toString())); 1131 lexer.token(")"); 1132 } else if (lexer.hasToken("(")) { 1133 target.setTransform(StructureMapTransform.fromCode(name)); 1134 lexer.token("("); 1135 if (target.getTransform() == StructureMapTransform.EVALUATE) { 1136 parseParameter(target, lexer); 1137 lexer.token(","); 1138 ExpressionNode node = fpe.parse(lexer); 1139 target.setUserData(MAP_EXPRESSION, node); 1140 target.addParameter().setValue(new StringType(node.toString())); 1141 } else { 1142 while (!lexer.hasToken(")")) { 1143 parseParameter(target, lexer); 1144 if (!lexer.hasToken(")")) 1145 lexer.token(","); 1146 } 1147 } 1148 lexer.token(")"); 1149 } else if (name != null) { 1150 if (target.getContext() != null) { 1151 target.setTransform(StructureMapTransform.COPY); 1152 if (!isConstant) { 1153 String id = name; 1154 while (lexer.hasToken(".")) { 1155 id = id + lexer.take() + lexer.take(); 1156 } 1157 target.addParameter().setValue(new IdType(id)); 1158 } else 1159 target.addParameter().setValue(readConstant(name, lexer)); 1160 } else { 1161 target.setContext(name); 1162 } 1163 } 1164 if (lexer.hasToken("as")) { 1165 lexer.take(); 1166 target.setVariable(lexer.take()); 1167 } 1168 while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { 1169 if (lexer.getCurrent().equals("share")) { 1170 target.addListMode(StructureMapTargetListMode.SHARE); 1171 lexer.next(); 1172 target.setListRuleId(lexer.take()); 1173 } else { 1174 if (lexer.getCurrent().equals("first")) 1175 target.addListMode(StructureMapTargetListMode.FIRST); 1176 else 1177 target.addListMode(StructureMapTargetListMode.LAST); 1178 lexer.next(); 1179 } 1180 } 1181 } 1182 1183 1184 private void parseParameter(StructureMapGroupRuleDependentComponent ref, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { 1185 if (!lexer.isConstant()) { 1186 ref.addParameter().setValue(new IdType(lexer.take())); 1187 } else if (lexer.isStringConstant()) 1188 ref.addParameter().setValue(new StringType(lexer.readConstant("??"))); 1189 else { 1190 ref.addParameter().setValue(readConstant(lexer.take(), lexer)); 1191 } 1192 } 1193 1194 private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { 1195 if (!lexer.isConstant()) { 1196 target.addParameter().setValue(new IdType(lexer.take())); 1197 } else if (lexer.isStringConstant()) 1198 target.addParameter().setValue(new StringType(lexer.readConstant("??"))); 1199 else { 1200 target.addParameter().setValue(readConstant(lexer.take(), lexer)); 1201 } 1202 } 1203 1204 1205 private DataType readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 1206 if (Utilities.isInteger(s)) 1207 return new IntegerType(s); 1208 else if (Utilities.isDecimal(s, false)) 1209 return new DecimalType(s); 1210 else if (Utilities.existsInList(s, "true", "false")) 1211 return new BooleanType(s.equals("true")); 1212 else 1213 return new StringType(lexer.processConstant(s)); 1214 } 1215 1216 1217 public StructureDefinition getTargetType(StructureMap map) throws FHIRException { 1218 boolean found = false; 1219 StructureDefinition res = null; 1220 for (StructureMapStructureComponent uses : map.getStructure()) { 1221 if (uses.getMode() == StructureMapModelMode.TARGET) { 1222 if (found) 1223 throw new FHIRException("Multiple targets found in map " + map.getUrl()); 1224 found = true; 1225 res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); 1226 if (res == null) 1227 throw new FHIRException("Unable to find " + uses.getUrl() + " referenced from map " + map.getUrl()); 1228 } 1229 } 1230 if (res == null) 1231 throw new FHIRException("No targets found in map " + map.getUrl()); 1232 return res; 1233 } 1234 1235 private void log(String cnt) { 1236 if (debug) { 1237 if (getServices() != null) 1238 getServices().log(cnt); 1239 else 1240 System.out.println(cnt); 1241 } 1242 } 1243 1244 /** 1245 * Given an item, return all the children that conform to the pattern described in name 1246 * <p> 1247 * Possible patterns: 1248 * - a simple name (which may be the base of a name with [] e.g. value[x]) 1249 * - a name with a type replacement e.g. valueCodeableConcept 1250 * - * which means all children 1251 * - ** which means all descendents 1252 * 1253 * @param item 1254 * @param name 1255 * @param result 1256 * @throws FHIRException 1257 */ 1258 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 1259 for (Base v : item.listChildrenByName(name, true)) 1260 if (v != null) 1261 result.add(v); 1262 } 1263 1264 public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { 1265 TransformContext context = new TransformContext(appInfo); 1266 log("Start Transform " + map.getUrl()); 1267 StructureMapGroupComponent g = map.getGroup().get(0); 1268 1269 Variables vars = new Variables(); 1270 vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); 1271 if (target != null) 1272 vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); 1273 else if (getInputName(g, StructureMapInputMode.TARGET, null) != null) { 1274 String type = getInputType(g, StructureMapInputMode.TARGET); 1275 throw new FHIRException("not handled yet: creating a type of " + type); 1276 } 1277 1278 executeGroup("", context, map, vars, g, true); 1279 if (target instanceof Element) 1280 ((Element) target).sort(); 1281 } 1282 1283 private String getInputType(StructureMapGroupComponent g, StructureMapInputMode mode) { 1284 String type = null; 1285 for (StructureMapGroupInputComponent inp : g.getInput()) { 1286 if (inp.getMode() == mode) 1287 if (type != null) 1288 throw new DefinitionException("This engine does not support multiple source inputs"); 1289 else 1290 type = inp.getType(); 1291 } 1292 return type; 1293 } 1294 1295 private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { 1296 String name = null; 1297 for (StructureMapGroupInputComponent inp : g.getInput()) { 1298 if (inp.getMode() == mode) 1299 if (name != null) 1300 throw new DefinitionException("This engine does not support multiple source inputs"); 1301 else 1302 name = inp.getName(); 1303 } 1304 return name == null ? def : name; 1305 } 1306 1307 private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException { 1308 log(indent + "Group : " + group.getName() + "; vars = " + vars.summary()); 1309 // todo: check inputs 1310 if (group.hasExtends()) { 1311 ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); 1312 executeGroup(indent + " ", context, rg.getTargetMap(), vars, rg.getTargetGroup(), false); 1313 } 1314 1315 for (StructureMapGroupRuleComponent r : group.getRule()) { 1316 executeRule(indent + " ", context, map, vars, group, r, atRoot); 1317 } 1318 } 1319 1320 private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException { 1321 log(indent + "rule : " + rule.getName() + "; vars = " + vars.summary()); 1322 Variables srcVars = vars.copy(); 1323 if (rule.getSource().size() != 1) 1324 throw new FHIRException("Rule \"" + rule.getName() + "\": not handled yet"); 1325 List<Variables> source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl(), indent); 1326 if (source != null) { 1327 for (Variables v : source) { 1328 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 1329 processTarget(map.getName()+"|"+group.getName()+"|"+rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars); 1330 } 1331 if (rule.hasRule()) { 1332 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 1333 executeRule(indent + " ", context, map, v, group, childrule, false); 1334 } 1335 } else if (rule.hasDependent()) { 1336 for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 1337 executeDependency(indent + " ", context, map, v, group, dependent); 1338 } 1339 } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { 1340 // simple inferred, map by type 1341 if (debug) { 1342 log(v.summary()); 1343 } 1344 Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); 1345 Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); 1346 String srcType = src.fhirType(); 1347 String tgtType = tgt.fhirType(); 1348 ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); 1349 Variables vdef = new Variables(); 1350 vdef.add(VariableMode.INPUT, defGroup.getTargetGroup().getInput().get(0).getName(), src); 1351 vdef.add(VariableMode.OUTPUT, defGroup.getTargetGroup().getInput().get(1).getName(), tgt); 1352 executeGroup(indent + " ", context, defGroup.getTargetMap(), vdef, defGroup.getTargetGroup(), false); 1353 } 1354 } 1355 } 1356 } 1357 1358 private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { 1359 ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); 1360 1361 if (rg.getTargetGroup().getInput().size() != dependent.getParameter().size()) { 1362 throw new FHIRException("Rule '" + dependent.getName() + "' has " + rg.getTargetGroup().getInput().size() + " but the invocation has " + dependent.getParameter().size() + " variables"); 1363 } 1364 Variables v = new Variables(); 1365 for (int i = 0; i < rg.getTargetGroup().getInput().size(); i++) { 1366 StructureMapGroupInputComponent input = rg.getTargetGroup().getInput().get(i); 1367 StructureMapGroupRuleTargetParameterComponent rdp = dependent.getParameter().get(i); 1368 String var = rdp.getValue().primitiveValue(); 1369 VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; 1370 Base vv = vin.get(mode, var); 1371 if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient 1372 vv = vin.get(VariableMode.OUTPUT, var); 1373 if (vv == null) 1374 throw new FHIRException("Rule '" + dependent.getName() + "' " + mode.toString() + " variable '" + input.getName() + "' named as '" + var + "' has no value (vars = " + vin.summary() + ")"); 1375 v.add(mode, input.getName(), vv); 1376 } 1377 executeGroup(indent + " ", context, rg.getTargetMap(), v, rg.getTargetGroup(), false); 1378 } 1379 1380 private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { 1381 String type = base.fhirType(); 1382 String kn = "type^" + type; 1383 if (source.hasUserData(kn)) 1384 return source.getUserString(kn); 1385 1386 ResolvedGroup res = new ResolvedGroup(null, null); 1387 for (StructureMapGroupComponent grp : map.getGroup()) { 1388 if (matchesByType(map, grp, type)) { 1389 if (res.getTargetMap() == null) { 1390 res.setTargetMap(map); 1391 res.setTargetGroup(grp); 1392 } else 1393 throw new FHIRException("Multiple possible matches looking for default rule for '" + type + "'"); 1394 } 1395 } 1396 if (res.getTargetMap() != null) { 1397 String result = getActualType(res.getTargetMap(), res.getTargetGroup().getInput().get(1).getType()); 1398 source.setUserData(kn, result); 1399 return result; 1400 } 1401 1402 for (UriType imp : map.getImport()) { 1403 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1404 if (impMapList.size() == 0) 1405 throw new FHIRException("Unable to find map(s) for " + imp.getValue()); 1406 for (StructureMap impMap : impMapList) { 1407 if (!impMap.getUrl().equals(map.getUrl())) { 1408 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1409 if (matchesByType(impMap, grp, type)) { 1410 if (res.getTargetMap() == null) { 1411 res.setTargetMap(impMap); 1412 res.setTargetGroup(grp); 1413 } else 1414 throw new FHIRException("Multiple possible matches for default rule for '" + type + "' in " + res.getTargetMap().getUrl() + " (" + res.getTargetGroup().getName() + ") and " + impMap.getUrl() + " (" + grp.getName() + ")"); 1415 } 1416 } 1417 } 1418 } 1419 } 1420 if (res.getTargetGroup() == null) 1421 throw new FHIRException("No matches found for default rule for '" + type + "' from " + map.getUrl()); 1422 String result = getActualType(res.getTargetMap(), res.getTargetGroup().getInput().get(1).getType()); // should be .getType, but R2... 1423 source.setUserData(kn, result); 1424 return result; 1425 } 1426 1427 private List<StructureMap> findMatchingMaps(String value) { 1428 List<StructureMap> res = new ArrayList<StructureMap>(); 1429 if (value.contains("*")) { 1430 for (StructureMap sm : worker.fetchResourcesByType(StructureMap.class)) { 1431 if (urlMatches(value, sm.getUrl())) { 1432 res.add(sm); 1433 } 1434 } 1435 } else { 1436 StructureMap sm = worker.fetchResource(StructureMap.class, value); 1437 if (sm != null) 1438 res.add(sm); 1439 } 1440 Set<String> check = new HashSet<String>(); 1441 for (StructureMap sm : res) { 1442 if (check.contains(sm.getUrl())) 1443 throw new FHIRException("duplicate"); 1444 else 1445 check.add(sm.getUrl()); 1446 } 1447 return res; 1448 } 1449 1450 private boolean urlMatches(String mask, String url) { 1451 return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*") + 1)); 1452 } 1453 1454 private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { 1455 String kn = "types^" + srcType + ":" + tgtType; 1456 if (source.hasUserData(kn)) 1457 return (ResolvedGroup) source.getUserData(kn); 1458 1459 ResolvedGroup res = new ResolvedGroup(null, null); 1460 for (StructureMapGroupComponent grp : map.getGroup()) { 1461 if (matchesByType(map, grp, srcType, tgtType)) { 1462 if (res.getTargetMap() == null) { 1463 res.setTargetMap(map); 1464 res.setTargetGroup(grp); 1465 } else 1466 throw new FHIRException("Multiple possible matches looking for rule for '" + srcType + "/" + tgtType + "', from rule '" + ruleid + "'"); 1467 } 1468 } 1469 if (res.getTargetMap() != null) { 1470 source.setUserData(kn, res); 1471 return res; 1472 } 1473 1474 for (UriType imp : map.getImport()) { 1475 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1476 if (impMapList.size() == 0) 1477 throw new FHIRException("Unable to find map(s) for " + imp.getValue()); 1478 for (StructureMap impMap : impMapList) { 1479 if (!impMap.getUrl().equals(map.getUrl())) { 1480 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1481 if (matchesByType(impMap, grp, srcType, tgtType)) { 1482 if (res.getTargetMap() == null) { 1483 res.setTargetMap(impMap); 1484 res.setTargetGroup(grp); 1485 } else 1486 throw new FHIRException("Multiple possible matches for rule for '" + srcType + "/" + tgtType + "' in " + res.getTargetMap().getUrl() + " and " + impMap.getUrl() + ", from rule '" + ruleid + "'"); 1487 } 1488 } 1489 } 1490 } 1491 } 1492 if (res.getTargetGroup() == null) 1493 throw new FHIRException("No matches found for rule for '" + srcType + " to " + tgtType + "' from " + map.getUrl() + ", from rule '" + ruleid + "'"); 1494 source.setUserData(kn, res); 1495 return res; 1496 } 1497 1498 1499 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { 1500 if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) 1501 return false; 1502 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1503 return false; 1504 return matchesType(map, type, grp.getInput().get(0).getType()); 1505 } 1506 1507 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { 1508 if (!grp.hasTypeMode()) 1509 return false; 1510 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1511 return false; 1512 if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) 1513 return false; 1514 return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); 1515 } 1516 1517 private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { 1518 // check the aliases 1519 for (StructureMapStructureComponent imp : map.getStructure()) { 1520 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1521 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1522 if (sd != null) 1523 statedType = sd.getType(); 1524 break; 1525 } 1526 } 1527 1528 if (Utilities.isAbsoluteUrl(actualType)) { 1529 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType); 1530 if (sd != null) 1531 actualType = sd.getType(); 1532 } 1533 if (Utilities.isAbsoluteUrl(statedType)) { 1534 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType); 1535 if (sd != null) 1536 statedType = sd.getType(); 1537 } 1538 return actualType.equals(statedType); 1539 } 1540 1541 private String getActualType(StructureMap map, String statedType) throws FHIRException { 1542 // check the aliases 1543 for (StructureMapStructureComponent imp : map.getStructure()) { 1544 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1545 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1546 if (sd == null) 1547 throw new FHIRException("Unable to resolve structure " + imp.getUrl()); 1548 return sd.getId(); // should be sd.getType(), but R2... 1549 } 1550 } 1551 return statedType; 1552 } 1553 1554 1555 private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { 1556 String kn = "ref^" + name; 1557 if (source.hasUserData(kn)) 1558 return (ResolvedGroup) source.getUserData(kn); 1559 1560 ResolvedGroup res = new ResolvedGroup(null, null); 1561 for (StructureMapGroupComponent grp : map.getGroup()) { 1562 if (grp.getName().equals(name)) { 1563 if (res.getTargetMap() == null) { 1564 res.setTargetMap(map); 1565 res.setTargetGroup(grp); 1566 } else 1567 throw new FHIRException("Multiple possible matches for rule '" + name + "'"); 1568 } 1569 } 1570 if (res.getTargetMap() != null) { 1571 source.setUserData(kn, res); 1572 return res; 1573 } 1574 1575 for (UriType imp : map.getImport()) { 1576 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1577 if (impMapList.size() == 0) 1578 throw new FHIRException("Unable to find map(s) for " + imp.getValue()); 1579 for (StructureMap impMap : impMapList) { 1580 if (!impMap.getUrl().equals(map.getUrl())) { 1581 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1582 if (grp.getName().equals(name)) { 1583 if (res.getTargetMap() == null) { 1584 res.setTargetMap(impMap); 1585 res.setTargetGroup(grp); 1586 } else 1587 throw new FHIRException("Multiple possible matches for rule group '" + name + "' in " + 1588 res.getTargetMap().getUrl() + "#" + res.getTargetGroup().getName() + " and " + 1589 impMap.getUrl() + "#" + grp.getName()); 1590 } 1591 } 1592 } 1593 } 1594 } 1595 if (res.getTargetGroup() == null) 1596 throw new FHIRException("No matches found for rule '" + name + "'. Reference found in " + map.getUrl()); 1597 source.setUserData(kn, res); 1598 return res; 1599 } 1600 1601 private List<Variables> processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors, String indent) throws FHIRException { 1602 List<Base> items; 1603 if (src.getContext().equals("@search")) { 1604 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); 1605 if (expr == null) { 1606 expr = fpe.parse(src.getElement()); 1607 src.setUserData(MAP_SEARCH_EXPRESSION, expr); 1608 } 1609 String search = fpe.evaluateToString(vars, null, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly 1610 items = services.performSearch(context.getAppInfo(), search); 1611 } else { 1612 items = new ArrayList<Base>(); 1613 Base b = vars.get(VariableMode.INPUT, src.getContext()); 1614 if (b == null) 1615 throw new FHIRException("Unknown input variable " + src.getContext() + " in " + pathForErrors + " rule " + ruleId + " (vars = " + vars.summary() + ")"); 1616 1617 if (!src.hasElement()) 1618 items.add(b); 1619 else { 1620 getChildrenByName(b, src.getElement(), items); 1621 if (items.size() == 0 && src.hasDefaultValue()) 1622 items.add(src.getDefaultValueElement()); 1623 } 1624 } 1625 1626 if (src.hasType()) { 1627 List<Base> remove = new ArrayList<Base>(); 1628 for (Base item : items) { 1629 if (item != null && !isType(item, src.getType())) { 1630 remove.add(item); 1631 } 1632 } 1633 items.removeAll(remove); 1634 } 1635 1636 if (src.hasCondition()) { 1637 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); 1638 if (expr == null) { 1639 expr = fpe.parse(src.getCondition()); 1640 src.setUserData(MAP_WHERE_EXPRESSION, expr); 1641 } 1642 List<Base> remove = new ArrayList<Base>(); 1643 for (Base item : items) { 1644 Variables varsForSource = vars.copy(); 1645 if (src.hasVariable()) { 1646 varsForSource.add(VariableMode.INPUT, src.getVariable(), item); 1647 } 1648 if (!fpe.evaluateToBoolean(varsForSource, null, null, item, expr)) { 1649 log(indent + " condition [" + src.getCondition() + "] for " + item.toString() + " : false"); 1650 remove.add(item); 1651 } else 1652 log(indent + " condition [" + src.getCondition() + "] for " + item.toString() + " : true"); 1653 } 1654 items.removeAll(remove); 1655 } 1656 1657 if (src.hasCheck()) { 1658 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); 1659 if (expr == null) { 1660 expr = fpe.parse(src.getCheck()); 1661 src.setUserData(MAP_WHERE_CHECK, expr); 1662 } 1663 for (Base item : items) { 1664 Variables varsForSource = vars.copy(); 1665 if (src.hasVariable()) { 1666 varsForSource.add(VariableMode.INPUT, src.getVariable(), item); 1667 } 1668 if (!fpe.evaluateToBoolean(varsForSource, null, null, item, expr)) 1669 throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed"); 1670 } 1671 } 1672 1673 if (src.hasLogMessage()) { 1674 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG); 1675 if (expr == null) { 1676 expr = fpe.parse(src.getLogMessage()); 1677 src.setUserData(MAP_WHERE_LOG, expr); 1678 } 1679 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1680 for (Base item : items) { 1681 Variables varsForSource = vars.copy(); 1682 if (src.hasVariable()) { 1683 varsForSource.add(VariableMode.INPUT, src.getVariable(), item); 1684 } 1685 b.appendIfNotNull(fpe.evaluateToString(varsForSource, null, null, item, expr)); 1686 } 1687 if (b.length() > 0) 1688 services.log(b.toString()); 1689 } 1690 1691 1692 if (src.hasListMode() && !items.isEmpty()) { 1693 switch (src.getListMode()) { 1694 case FIRST: 1695 Base bt = items.get(0); 1696 items.clear(); 1697 items.add(bt); 1698 break; 1699 case NOTFIRST: 1700 if (items.size() > 0) 1701 items.remove(0); 1702 break; 1703 case LAST: 1704 bt = items.get(items.size() - 1); 1705 items.clear(); 1706 items.add(bt); 1707 break; 1708 case NOTLAST: 1709 if (items.size() > 0) 1710 items.remove(items.size() - 1); 1711 break; 1712 case ONLYONE: 1713 if (items.size() > 1) 1714 throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed: the collection has more than one item"); 1715 break; 1716 case NULL: 1717 } 1718 } 1719 List<Variables> result = new ArrayList<Variables>(); 1720 for (Base r : items) { 1721 Variables v = vars.copy(); 1722 if (src.hasVariable()) 1723 v.add(VariableMode.INPUT, src.getVariable(), r); 1724 result.add(v); 1725 } 1726 return result; 1727 } 1728 1729 1730 private boolean isType(Base item, String type) { 1731 return type.equals(item.fhirType()); 1732 } 1733 1734 private void processTarget(String rulePath, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException { 1735 Base dest = null; 1736 if (tgt.hasContext()) { 1737 dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); 1738 if (dest == null) { 1739 throw new FHIRException("Rul \"" + rulePath + "\": target context not known: " + tgt.getContext()); 1740 } 1741 } 1742 Base v = null; 1743 if (tgt.hasTransform()) { 1744 v = runTransform(rulePath, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); 1745 if (v != null && dest != null) { 1746 try { 1747 v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value 1748 } catch (Exception e) { 1749 throw new FHIRException("Error setting "+tgt.getElement()+" on "+dest.fhirType()+" for rule "+rulePath+" to value "+v.toString()+": "+e.getMessage(), e); 1750 } 1751 } 1752 } else if (dest != null) { 1753 if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) { 1754 v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId()); 1755 if (v == null) { 1756 v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); 1757 sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v); 1758 } 1759 } else if (tgt.hasElement()) { 1760 v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); 1761 } else { 1762 v = dest; 1763 } 1764 } 1765 if (tgt.hasVariable() && v != null) 1766 vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); 1767 } 1768 1769 private Base runTransform(String rulePath, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException { 1770 try { 1771 switch (tgt.getTransform()) { 1772 case CREATE: 1773 String tn; 1774 if (tgt.getParameter().isEmpty()) { 1775 // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that 1776 String[] types = dest.getTypesForProperty(element.hashCode(), element); 1777 if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) 1778 tn = types[0]; 1779 else if (srcVar != null) { 1780 tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); 1781 } else 1782 throw new FHIRException("Cannot determine type implicitly because there is no single input variable"); 1783 } else { 1784 tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); 1785 // ok, now we resolve the type name against the import statements 1786 for (StructureMapStructureComponent uses : map.getStructure()) { 1787 if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) { 1788 tn = uses.getUrl(); 1789 break; 1790 } 1791 } 1792 } 1793 Base res = services != null ? services.createType(context.getAppInfo(), tn, profileUtilities) : typeFactory(tn); 1794 if (res.isResource() && !res.fhirType().equals("Parameters")) { 1795// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); 1796 if (services != null) 1797 res = services.createResource(context.getAppInfo(), res, root); 1798 } 1799 if (tgt.hasUserData(UserDataNames.map_profile)) 1800 res.setUserData(UserDataNames.map_profile, tgt.getUserData(UserDataNames.map_profile)); 1801 return res; 1802 case COPY: 1803 return getParam(vars, tgt.getParameter().get(0)); 1804 case EVALUATE: 1805 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 1806 if (expr == null) { 1807 expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(tgt.getParameter().size() - 1), tgt.toString())); 1808 tgt.setUserData(MAP_EXPRESSION, expr); 1809 } 1810 List<Base> v = fpe.evaluate(vars, null, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); 1811 if (v.size() == 0) 1812 return null; 1813 else if (v.size() != 1) 1814 throw new FHIRException("Rule \"" + rulePath+ "\": Evaluation of " + expr.toString() + " returned " + v.size() + " objects"); 1815 else 1816 return v.get(0); 1817 1818 case TRUNCATE: 1819 String src = getParamString(vars, tgt.getParameter().get(0)); 1820 String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); 1821 if (Utilities.isInteger(len)) { 1822 int l = Integer.parseInt(len); 1823 if (src.length() > l) 1824 src = src.substring(0, l); 1825 } 1826 return new StringType(src); 1827 case ESCAPE: 1828 throw new FHIRException("Rule \"" + rulePath + "\": Transform " + tgt.getTransform().toCode() + " not supported yet"); 1829 case CAST: 1830 src = getParamString(vars, tgt.getParameter().get(0)); 1831 if (tgt.getParameter().size() == 1) 1832 throw new FHIRException("Implicit type parameters on cast not yet supported"); 1833 String t = getParamString(vars, tgt.getParameter().get(1)); 1834 switch(t) { 1835 case "boolean": 1836 return new BooleanType(src); 1837 case "integer": 1838 return new IntegerType(src); 1839 case "integer64": 1840 return new Integer64Type(src); 1841 case "string": 1842 return new StringType(src); 1843 case "decimal": 1844 return new DecimalType(src); 1845 case "uri": 1846 return new UriType(src); 1847 case "base64Binary": 1848 return new Base64BinaryType(src); 1849 case "instant": 1850 return new InstantType(src); 1851 case "date": 1852 return new DateType(src); 1853 case "dateTime": 1854 return new DateTimeType(src); 1855 case "time": 1856 return new TimeType(src); 1857 case "code": 1858 return new CodeType(src); 1859 case "oid": 1860 return new OidType(src); 1861 case "id": 1862 return new IdType(src); 1863 case "markdown": 1864 return new MarkdownType(src); 1865 case "unsignedInt": 1866 return new UnsignedIntType(src); 1867 case "positiveInt": 1868 return new PositiveIntType(src); 1869 case "uuid": 1870 return new UuidType(src); 1871 case "url": 1872 return new UrlType(src); 1873 case "canonical": 1874 return new CanonicalType(src); 1875 } 1876 throw new FHIRException("cast to " + t + " not yet supported"); 1877 case APPEND: 1878 StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0))); 1879 for (int i = 1; i < tgt.getParameter().size(); i++) 1880 sb.append(getParamString(vars, tgt.getParameter().get(i))); 1881 return new StringType(sb.toString()); 1882 case TRANSLATE: 1883 return translate(context, map, vars, tgt.getParameter()); 1884 case REFERENCE: 1885 Base b = getParam(vars, tgt.getParameter().get(0)); 1886 if (b == null) 1887 throw new FHIRException("Rule \"" + rulePath + "\": Unable to find parameter " + ((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); 1888 if (!b.isResource()) 1889 throw new FHIRException("Rule \"" + rulePath + "\": Transform engine cannot point at an element of type " + b.fhirType()); 1890 else { 1891 String id = b.getIdBase(); 1892 if (id == null) { 1893 id = UUID.randomUUID().toString().toLowerCase(); 1894 b.setIdBase(id); 1895 } 1896 return new StringType(b.fhirType() + "/" + id); 1897 } 1898 case DATEOP: 1899 throw new FHIRException("Rule \"" + rulePath + "\": Transform " + tgt.getTransform().toCode() + " not supported yet"); 1900 case UUID: 1901 return new IdType(UUID.randomUUID().toString()); 1902 case POINTER: 1903 b = getParam(vars, tgt.getParameter().get(0)); 1904 if (b instanceof Resource) 1905 return new UriType("urn:uuid:" + ((Resource) b).getId()); 1906 else 1907 throw new FHIRException("Rule \"" + rulePath + "\": Transform engine cannot point at an element of type " + b.fhirType()); 1908 case CC: 1909 CodeableConcept cc = new CodeableConcept(); 1910 cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); 1911 return cc; 1912 case C: 1913 Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); 1914 return c; 1915 default: 1916 throw new FHIRException("Rule \"" + rulePath + "\": Transform Unknown: " + tgt.getTransform().toCode()); 1917 } 1918 } catch (Exception e) { 1919 throw new FHIRException("Exception executing transform " + tgt.toString() + " on Rule \"" + rulePath + "\": " + e.getMessage(), e); 1920 } 1921 } 1922 1923 private Base typeFactory(String tn) { 1924 if (Utilities.isAbsoluteUrl(tn) && !tn.startsWith("http://hl7.org/fhir/StructureDefinition")) { 1925 StructureDefinition sd = worker.fetchTypeDefinition(tn); 1926 if (sd == null) { 1927 if (Utilities.existsInList(tn, "http://hl7.org/fhirpath/System.String")) { 1928 sd = worker.fetchTypeDefinition("string"); 1929 } 1930 } 1931 if (sd == null) { 1932 throw new FHIRException("Unable to create type "+tn); 1933 } else { 1934 return Manager.build(worker, sd, profileUtilities); 1935 } 1936 } else { 1937 return ResourceFactory.createResourceOrType(tn); 1938 } 1939 } 1940 1941 1942 private Coding buildCoding(String uri, String code) throws FHIRException { 1943 // if we can get this as a valueSet, we will 1944 String system = null; 1945 String display = null; 1946 String version = null; 1947 ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); 1948 if (vs != null) { 1949 ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); 1950 if (vse.getError() != null) 1951 throw new FHIRException(vse.getError()); 1952 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1953 for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { 1954 if (t.hasCode()) 1955 b.append(t.getCode()); 1956 if (code.equals(t.getCode()) && t.hasSystem()) { 1957 system = t.getSystem(); 1958 version = t.getVersion(); 1959 display = t.getDisplay(); 1960 break; 1961 } 1962 if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { 1963 system = t.getSystem(); 1964 version = t.getVersion(); 1965 display = t.getDisplay(); 1966 break; 1967 } 1968 } 1969 if (system == null) 1970 throw new FHIRException("The code '" + code + "' is not in the value set '" + uri + "' (valid codes: " + b.toString() + "; also checked displays)"); 1971 } else { 1972 system = uri; 1973 } 1974 ValidationResult vr = worker.validateCode(terminologyServiceOptions.withVersionFlexible(true), system, version, code, null); 1975 if (vr != null && vr.getDisplay() != null) 1976 display = vr.getDisplay(); 1977 return new Coding().setSystem(system).setCode(code).setDisplay(display); 1978 } 1979 1980 1981 private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { 1982 Base b = getParam(vars, parameter); 1983 if (b == null) 1984 throw new FHIRException("Unable to find a value for " + parameter.toString() + ". Context: " + message); 1985 if (!b.hasPrimitiveValue()) 1986 throw new FHIRException("Found a value for " + parameter.toString() + ", but it has a type of " + b.fhirType() + " and cannot be treated as a string. Context: " + message); 1987 return b.primitiveValue(); 1988 } 1989 1990 private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1991 Base b = getParam(vars, parameter); 1992 if (b == null || !b.hasPrimitiveValue()) 1993 return null; 1994 return b.primitiveValue(); 1995 } 1996 1997 1998 private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1999 DataType p = parameter.getValue(); 2000 if (!(p instanceof IdType)) 2001 return p; 2002 else { 2003 String n = ((IdType) p).asStringValue(); 2004 Base b = vars.get(VariableMode.INPUT, n); 2005 if (b == null) 2006 b = vars.get(VariableMode.OUTPUT, n); 2007 if (b == null) 2008 throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")"); 2009 return b; 2010 } 2011 } 2012 2013 2014 private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException { 2015 Base src = getParam(vars, parameter.get(0)); 2016 String id = getParamString(vars, parameter.get(1)); 2017 String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; 2018 return translate(context, map, src, id, fld); 2019 } 2020 2021 public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { 2022 Coding src = new Coding(); 2023 if (source.isPrimitive()) { 2024 src.setCode(source.primitiveValue()); 2025 } else if ("Coding".equals(source.fhirType())) { 2026 Base[] b = source.getProperty("system".hashCode(), "system", true); 2027 if (b.length == 1) 2028 src.setSystem(b[0].primitiveValue()); 2029 b = source.getProperty("code".hashCode(), "code", true); 2030 if (b.length == 1) 2031 src.setCode(b[0].primitiveValue()); 2032 } else if ("CE".equals(source.fhirType())) { 2033 Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); 2034 if (b.length == 1) 2035 src.setSystem(b[0].primitiveValue()); 2036 b = source.getProperty("code".hashCode(), "code", true); 2037 if (b.length == 1) 2038 src.setCode(b[0].primitiveValue()); 2039 } else 2040 throw new FHIRException("Unable to translate source " + source.fhirType()); 2041 2042 String su = conceptMapUrl; 2043 if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { 2044 String uri = new ContextUtilities(worker).oid2Uri(src.getCode()); 2045 if (uri == null) 2046 uri = "urn:oid:" + src.getCode(); 2047 if ("uri".equals(fieldToReturn)) 2048 return new UriType(uri); 2049 else 2050 throw new FHIRException("Error in return code"); 2051 } else { 2052 ConceptMap cmap = null; 2053 if (conceptMapUrl.startsWith("#")) { 2054 for (Resource r : map.getContained()) { 2055 if (r instanceof ConceptMap && r.getId().equals(conceptMapUrl.substring(1))) { 2056 cmap = (ConceptMap) r; 2057 su = map.getUrl() + "#" + conceptMapUrl; 2058 } 2059 } 2060 if (cmap == null) 2061 throw new FHIRException("Unable to translate - cannot find map " + conceptMapUrl); 2062 } else { 2063 if (conceptMapUrl.contains("#")) { 2064 String[] p = conceptMapUrl.split("\\#"); 2065 StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]); 2066 for (Resource r : mapU.getContained()) { 2067 if (r instanceof ConceptMap && r.getId().equals(p[1])) { 2068 cmap = (ConceptMap) r; 2069 su = conceptMapUrl; 2070 } 2071 } 2072 } 2073 if (cmap == null) 2074 cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); 2075 } 2076 Coding outcome = null; 2077 boolean done = false; 2078 String message = null; 2079 if (cmap == null) { 2080 if (services == null) 2081 message = "No map found for " + conceptMapUrl; 2082 else { 2083 outcome = services.translate(context.getAppInfo(), src, conceptMapUrl); 2084 done = true; 2085 } 2086 } else { 2087 List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>(); 2088 for (ConceptMapGroupComponent g : cmap.getGroup()) { 2089 for (SourceElementComponent e : g.getElement()) { 2090 if (!src.hasSystem() && src.getCode().equals(e.getCode())) 2091 list.add(new SourceElementComponentWrapper(g, e)); 2092 else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) 2093 list.add(new SourceElementComponentWrapper(g, e)); 2094 } 2095 } 2096 if (list.size() == 0) 2097 done = true; 2098 else if (list.get(0).getComp().getTarget().size() == 0) 2099 message = "Concept map " + su + " found no translation for " + src.getCode(); 2100 else { 2101 for (TargetElementComponent tgt : list.get(0).getComp().getTarget()) { 2102 if (tgt.getRelationship() == null || EnumSet.of(ConceptMapRelationship.RELATEDTO, ConceptMapRelationship.EQUIVALENT, ConceptMapRelationship.SOURCEISNARROWERTHANTARGET).contains(tgt.getRelationship())) { 2103 if (done) { 2104 message = "Concept map " + su + " found multiple matches for " + src.getCode(); 2105 done = false; 2106 } else { 2107 done = true; 2108 outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).getGroup().getTarget()); 2109 } 2110 } 2111 } 2112 if (!done) 2113 message = "Concept map " + su + " found no usable translation for " + src.getCode(); 2114 } 2115 } 2116 if (!done) 2117 throw new FHIRException(message); 2118 if (outcome == null) 2119 return null; 2120 if ("code".equals(fieldToReturn)) 2121 return new CodeType(outcome.getCode()); 2122 else 2123 return outcome; 2124 } 2125 } 2126 2127 2128 /** 2129 * Given a structure map, return a set of analyses on it. 2130 * <p> 2131 * Returned: 2132 * - a list or profiles for what it will create. First profile is the target 2133 * - a table with a summary (in xhtml) for easy human undertanding of the mapping 2134 * 2135 * @param appInfo 2136 * @param map 2137 * @return 2138 * @throws Exception 2139 */ 2140 public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws FHIRException { 2141 ids.clear(); 2142 StructureMapAnalysis result = new StructureMapAnalysis(); 2143 TransformContext context = new TransformContext(appInfo); 2144 VariablesForProfiling vars = new VariablesForProfiling(this, false, false); 2145 if (map.hasGroup()) { 2146 StructureMapGroupComponent start = map.getGroup().get(0); 2147 for (StructureMapGroupInputComponent t : start.getInput()) { 2148 PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); 2149 if (t.getMode() == StructureMapInputMode.SOURCE) 2150 vars.add(VariableMode.INPUT, t.getName(), ti); 2151 else 2152 vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); 2153 } 2154 result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); 2155 XhtmlNode tr = result.summary.addTag("tr"); 2156 tr.addTag("td").addTag("b").addText("Source"); 2157 tr.addTag("td").addTag("b").addText("Target"); 2158 2159 log("Start Profiling Transform " + map.getUrl()); 2160 analyseGroup("", context, map, vars, start, result); 2161 } 2162 ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); 2163 for (StructureDefinition sd : result.getProfiles()) 2164 pu.cleanUpDifferential(sd); 2165 return result; 2166 } 2167 2168 2169 private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws FHIRException { 2170 log(indent + "Analyse Group : " + group.getName()); 2171 // todo: extends 2172 // todo: check inputs 2173 XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); 2174 XhtmlNode xs = tr.addTag("td"); 2175 XhtmlNode xt = tr.addTag("td"); 2176 for (StructureMapGroupInputComponent inp : group.getInput()) { 2177 if (inp.getMode() == StructureMapInputMode.SOURCE) 2178 noteInput(vars, inp, VariableMode.INPUT, xs); 2179 if (inp.getMode() == StructureMapInputMode.TARGET) 2180 noteInput(vars, inp, VariableMode.OUTPUT, xt); 2181 } 2182 for (StructureMapGroupRuleComponent r : group.getRule()) { 2183 analyseRule(indent + " ", context, map, vars, group, r, result); 2184 } 2185 } 2186 2187 2188 private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { 2189 VariableForProfiling v = vars.get(mode, inp.getName()); 2190 if (v != null) 2191 xs.addText("Input: " + v.getProperty().getPath()); 2192 } 2193 2194 private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws FHIRException { 2195 log(indent + "Analyse rule : " + rule.getName()); 2196 XhtmlNode tr = result.summary.addTag("tr"); 2197 XhtmlNode xs = tr.addTag("td"); 2198 XhtmlNode xt = tr.addTag("td"); 2199 2200 VariablesForProfiling srcVars = vars.copy(); 2201 if (rule.getSource().size() != 1) 2202 throw new FHIRException("Rule \"" + rule.getName() + "\": not handled yet"); 2203 VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); 2204 2205 TargetWriter tw = new TargetWriter(); 2206 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 2207 analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); 2208 } 2209 tw.commit(xt); 2210 2211 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 2212 analyseRule(indent + " ", context, map, source, group, childrule, result); 2213 } 2214// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 2215// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? 2216// } 2217 } 2218 2219 private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws FHIRException { 2220 VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); 2221 if (var == null) 2222 throw new FHIRException("Rule \"" + ruleId + "\": Unknown input variable " + src.getContext()); 2223 PropertyWithType prop = var.getProperty(); 2224 2225 boolean optional = false; 2226 boolean repeating = false; 2227 2228 if (src.hasCondition()) { 2229 optional = true; 2230 } 2231 2232 if (src.hasElement()) { 2233 Property element = prop.getBaseProperty().getChild(prop.getTypes().getType(), src.getElement()); 2234 if (element == null) 2235 throw new FHIRException("Rule \"" + ruleId + "\": Unknown element name " + src.getElement()); 2236 if (element.getDefinition().getMin() == 0) 2237 optional = true; 2238 if (element.getDefinition().getMax().equals("*")) 2239 repeating = true; 2240 VariablesForProfiling result = vars.copy(optional, repeating); 2241 TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); 2242 for (TypeRefComponent tr : element.getDefinition().getType()) { 2243 if (!tr.hasCode()) 2244 throw new FHIRException("Rule \"" + ruleId + "\": Element has no type"); 2245 ProfiledType pt = new ProfiledType(tr.getWorkingCode()); 2246 if (tr.hasProfile()) 2247 pt.addProfiles(tr.getProfile()); 2248 if (element.getDefinition().hasBinding()) 2249 pt.addBinding(element.getDefinition().getBinding()); 2250 type.addType(pt); 2251 } 2252 td.addText(prop.getPath() + "." + src.getElement()); 2253 if (src.hasVariable()) 2254 result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath() + "." + src.getElement(), element, null, type)); 2255 return result; 2256 } else { 2257 td.addText(prop.getPath()); // ditto! 2258 return vars.copy(optional, repeating); 2259 } 2260 } 2261 2262 2263 private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws FHIRException { 2264 VariableForProfiling var = null; 2265 if (tgt.hasContext()) { 2266 var = vars.get(VariableMode.OUTPUT, tgt.getContext()); 2267 if (var == null) 2268 throw new FHIRException("Rule \"" + ruleId + "\": target context not known: " + tgt.getContext()); 2269 if (!tgt.hasElement()) 2270 throw new FHIRException("Rule \"" + ruleId + "\": Not supported yet"); 2271 } 2272 2273 2274 TypeDetails type = null; 2275 if (tgt.hasTransform()) { 2276 type = analyseTransform(context, map, tgt, var, vars); 2277 } else { 2278 Property vp = var.getProperty().getBaseProperty().getChild(tgt.getElement(), tgt.getElement()); 2279 if (vp == null) 2280 throw new FHIRException("Unknown Property " + tgt.getElement() + " on " + var.getProperty().getPath()); 2281 2282 type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); 2283 } 2284 2285 if (tgt.getTransform() == StructureMapTransform.CREATE) { 2286 String s = getParamString(vars, tgt.getParameter().get(0)); 2287 if (worker.getResourceNames().contains(s)) 2288 tw.newResource(tgt.getVariable(), s); 2289 } else { 2290 boolean mapsSrc = false; 2291 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2292 DataType pr = p.getValue(); 2293 if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) 2294 mapsSrc = true; 2295 } 2296 if (mapsSrc) { 2297 if (var == null) 2298 throw new FHIRException("Rule \"" + ruleId + "\": Attempt to assign with no context"); 2299 tw.valueAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + getTransformSuffix(tgt.getTransform())); 2300 } else if (tgt.hasContext()) { 2301 if (isSignificantElement(var.getProperty(), tgt.getElement())) { 2302 String td = describeTransform(tgt); 2303 if (td != null) 2304 tw.keyAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + " = " + td); 2305 } 2306 } 2307 } 2308 DataType fixed = generateFixedValue(tgt); 2309 2310 PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); 2311 if (tgt.hasVariable()) 2312 if (tgt.hasElement()) 2313 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2314 else 2315 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2316 } 2317 2318 private DataType generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { 2319 if (!allParametersFixed(tgt)) 2320 return null; 2321 if (!tgt.hasTransform()) 2322 return null; 2323 switch (tgt.getTransform()) { 2324 case COPY: 2325 return tgt.getParameter().get(0).getValue(); 2326 case TRUNCATE: 2327 return null; 2328 //case ESCAPE: 2329 //case CAST: 2330 //case APPEND: 2331 case TRANSLATE: 2332 return null; 2333 //case DATEOP, 2334 //case UUID, 2335 //case POINTER, 2336 //case EVALUATE, 2337 case CC: 2338 CodeableConcept cc = new CodeableConcept(); 2339 cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); 2340 return cc; 2341 case C: 2342 return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); 2343 case QTY: 2344 return null; 2345 //case ID, 2346 //case CP, 2347 default: 2348 return null; 2349 } 2350 } 2351 2352 @SuppressWarnings("rawtypes") 2353 private Coding buildCoding(DataType value1, DataType value2) { 2354 return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()); 2355 } 2356 2357 private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { 2358 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2359 DataType pr = p.getValue(); 2360 if (pr instanceof IdType) 2361 return false; 2362 } 2363 return true; 2364 } 2365 2366 private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2367 switch (tgt.getTransform()) { 2368 case COPY: 2369 return null; 2370 case TRUNCATE: 2371 return null; 2372 //case ESCAPE: 2373 //case CAST: 2374 //case APPEND: 2375 case TRANSLATE: 2376 return null; 2377 //case DATEOP, 2378 //case UUID, 2379 //case POINTER, 2380 //case EVALUATE, 2381 case CC: 2382 return describeTransformCCorC(tgt); 2383 case C: 2384 return describeTransformCCorC(tgt); 2385 case QTY: 2386 return null; 2387 //case ID, 2388 //case CP, 2389 default: 2390 return null; 2391 } 2392 } 2393 2394 @SuppressWarnings("rawtypes") 2395 private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2396 if (tgt.getParameter().size() < 2) 2397 return null; 2398 DataType p1 = tgt.getParameter().get(0).getValue(); 2399 DataType p2 = tgt.getParameter().get(1).getValue(); 2400 if (p1 instanceof IdType || p2 instanceof IdType) 2401 return null; 2402 if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) 2403 return null; 2404 String uri = ((PrimitiveType) p1).asStringValue(); 2405 String code = ((PrimitiveType) p2).asStringValue(); 2406 if (Utilities.noString(uri)) 2407 throw new FHIRException("Describe Transform, but the uri is blank"); 2408 if (Utilities.noString(code)) 2409 throw new FHIRException("Describe Transform, but the code is blank"); 2410 Coding c = buildCoding(uri, code); 2411 return c.getSystem() + "#" + c.getCode() + (c.hasDisplay() ? "(" + c.getDisplay() + ")" : ""); 2412 } 2413 2414 2415 private boolean isSignificantElement(PropertyWithType property, String element) { 2416 if ("Observation".equals(property.getPath())) 2417 return "code".equals(element); 2418 else if ("Bundle".equals(property.getPath())) 2419 return "type".equals(element); 2420 else 2421 return false; 2422 } 2423 2424 private String getTransformSuffix(StructureMapTransform transform) { 2425 switch (transform) { 2426 case COPY: 2427 return ""; 2428 case TRUNCATE: 2429 return " (truncated)"; 2430 //case ESCAPE: 2431 //case CAST: 2432 //case APPEND: 2433 case TRANSLATE: 2434 return " (translated)"; 2435 //case DATEOP, 2436 //case UUID, 2437 //case POINTER, 2438 //case EVALUATE, 2439 case CC: 2440 return " (--> CodeableConcept)"; 2441 case C: 2442 return " (--> Coding)"; 2443 case QTY: 2444 return " (--> Quantity)"; 2445 //case ID, 2446 //case CP, 2447 default: 2448 return " {??)"; 2449 } 2450 } 2451 2452 private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, DataType fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2453 if (var == null) { 2454 assert (Utilities.noString(element)); 2455 // 1. start the new structure definition 2456 StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); 2457 if (sdn == null) 2458 throw new FHIRException("Unable to find definition for " + type.getType()); 2459 ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); 2460 PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); 2461 return pn; 2462 } else { 2463 assert (!Utilities.noString(element)); 2464 Property pvb = var.getProperty().getBaseProperty(); 2465 Property pvd = var.getProperty().getProfileProperty(); 2466 Property pc = pvb.getChild(element, var.getProperty().getTypes()); 2467 if (pc == null) 2468 throw new DefinitionException("Unable to find a definition for " + pvb.getDefinition().getPath() + "." + element); 2469 2470 // the profile structure definition (derived) 2471 StructureDefinition sd = var.getProperty().getProfileProperty().getStructure(); 2472 ElementDefinition ednew = sd.getDifferential().addElement(); 2473 ednew.setPath(var.getProperty().getProfileProperty().getDefinition().getPath() + "." + pc.getName()); 2474 ednew.setUserData(UserDataNames.MAP_slice_name, sliceName); // todo.. why do this? 2475 ednew.setFixed(fixed); 2476 for (ProfiledType pt : type.getProfiledTypes()) { 2477 if (pt.hasBindings()) 2478 ednew.setBinding(pt.getBindings().get(0)); 2479 if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2480 String t = pt.getUri().substring(40); 2481 t = checkType(t, pc, pt.getProfiles()); 2482 if (t != null) { 2483 if (pt.hasProfiles()) { 2484 for (String p : pt.getProfiles()) 2485 if (t.equals("Reference")) 2486 ednew.getType(t).addTargetProfile(p); 2487 else 2488 ednew.getType(t).addProfile(p); 2489 } else 2490 ednew.getType(t); 2491 } 2492 } 2493 } 2494 2495 return new PropertyWithType(var.getProperty().getPath() + "." + element, pc, new Property(worker, ednew, sd), type); 2496 } 2497 } 2498 2499 2500 private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException { 2501 if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getWorkingCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) 2502 return null; 2503 for (TypeRefComponent tr : pvb.getDefinition().getType()) { 2504 if (isCompatibleType(t, tr.getWorkingCode())) 2505 return tr.getWorkingCode(); // note what is returned - the base type, not the inferred mapping type 2506 } 2507 throw new FHIRException("The type " + t + " is not compatible with the allowed types for " + pvb.getDefinition().getPath()); 2508 } 2509 2510 private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) { 2511 return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue())); 2512 } 2513 2514 private boolean isCompatibleType(String t, String code) { 2515 if (t.equals(code)) 2516 return true; 2517 if (t.equals("string")) { 2518 StructureDefinition sd = worker.fetchTypeDefinition(code); 2519 return sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"); 2520 } 2521 return false; 2522 } 2523 2524 private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { 2525 switch (tgt.getTransform()) { 2526 case CREATE: 2527 String p = getParamString(vars, tgt.getParameter().get(0)); 2528 return new TypeDetails(CollectionStatus.SINGLETON, p); 2529 case COPY: 2530 return getParam(vars, tgt.getParameter().get(0)); 2531 case EVALUATE: 2532 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 2533 if (expr == null) { 2534 expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size() - 1))); 2535 } 2536 return fpe.check(vars, null, null, expr); 2537 case TRANSLATE: 2538 return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); 2539 case CC: 2540 ProfiledType res = new ProfiledType("CodeableConcept"); 2541 if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { 2542 TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).getProperty().getTypes(); 2543 if (td != null && td.hasBinding()) 2544 // todo: do we need to check that there's no implicit translation her? I don't think we do... 2545 res.addBinding(td.getBinding()); 2546 } 2547 return new TypeDetails(CollectionStatus.SINGLETON, res); 2548 case C: 2549 return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); 2550 case QTY: 2551 return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); 2552 case REFERENCE: 2553 VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); 2554 if (vrs == null) 2555 throw new FHIRException("Unable to resolve variable \"" + getParamId(vars, tgt.getParameterFirstRep()) + "\""); 2556 String profile = vrs.getProperty().getProfileProperty().getStructure().getUrl(); 2557 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); 2558 td.addType("Reference", profile); 2559 return td; 2560 case UUID: 2561 return new TypeDetails(CollectionStatus.SINGLETON, "id"); 2562 default: 2563 throw new FHIRException("Transform Unknown or not handled yet: " + tgt.getTransform().toCode()); 2564 } 2565 } 2566 2567 private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2568 DataType p = parameter.getValue(); 2569 if (p == null || p instanceof IdType) 2570 return null; 2571 if (!p.hasPrimitiveValue()) 2572 return null; 2573 return p.primitiveValue(); 2574 } 2575 2576 private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2577 DataType p = parameter.getValue(); 2578 if (p == null || !(p instanceof IdType)) 2579 return null; 2580 return p.primitiveValue(); 2581 } 2582 2583 private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2584 DataType p = parameter.getValue(); 2585 if (p == null || !(p instanceof IdType)) 2586 return false; 2587 return vars.get(null, p.primitiveValue()) != null; 2588 } 2589 2590 private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 2591 DataType p = parameter.getValue(); 2592 if (!(p instanceof IdType)) 2593 return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), null)); 2594 else { 2595 String n = ((IdType) p).asStringValue(); 2596 VariableForProfiling b = vars.get(VariableMode.INPUT, n); 2597 if (b == null) 2598 b = vars.get(VariableMode.OUTPUT, n); 2599 if (b == null) 2600 throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")"); 2601 return b.getProperty().getTypes(); 2602 } 2603 } 2604 2605 private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException { 2606 if (prop.getBaseProperty().getDefinition().getPath().contains(".")) 2607 throw new DefinitionException("Unable to process entry point"); 2608 2609 String type = prop.getBaseProperty().getDefinition().getPath(); 2610 String suffix = ""; 2611 if (ids.containsKey(type)) { 2612 int id = ids.get(type); 2613 id++; 2614 ids.put(type, id); 2615 suffix = "-" + id; 2616 } else 2617 ids.put(type, 0); 2618 2619 StructureDefinition profile = new StructureDefinition(); 2620 profiles.add(profile); 2621 profile.setDerivation(TypeDerivationRule.CONSTRAINT); 2622 profile.setType(type); 2623 profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); 2624 profile.setName("Profile for " + profile.getType() + " for " + sliceName); 2625 profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition") + "-" + profile.getType() + suffix); 2626 ctxt.setUserData(UserDataNames.map_profile, profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform 2627 profile.setId(map.getId() + "-" + profile.getType() + suffix); 2628 profile.setStatus(map.getStatus()); 2629 profile.setExperimental(map.getExperimental()); 2630 profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); 2631 for (ContactDetail c : map.getContact()) { 2632 ContactDetail p = profile.addContact(); 2633 p.setName(c.getName()); 2634 for (ContactPoint cc : c.getTelecom()) 2635 p.addTelecom(cc); 2636 } 2637 profile.setDate(map.getDate()); 2638 profile.setCopyright(map.getCopyright()); 2639 profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION)); 2640 profile.setKind(prop.getBaseProperty().getStructure().getKind()); 2641 profile.setAbstract(false); 2642 ElementDefinition ed = profile.getDifferential().addElement(); 2643 ed.setPath(profile.getType()); 2644 prop.setProfileProperty(new Property(worker, ed, profile)); 2645 return prop; 2646 } 2647 2648 private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws FHIRException { 2649 for (StructureMapStructureComponent imp : map.getStructure()) { 2650 if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || 2651 (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { 2652 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 2653 if (sd == null) 2654 throw new FHIRException("Import " + imp.getUrl() + " cannot be resolved"); 2655 if (sd.getId().equals(type)) { 2656 return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); 2657 } 2658 } 2659 } 2660 throw new FHIRException("Unable to find structure definition for " + type + " in imports"); 2661 } 2662 2663 2664 public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { 2665 String id = getLogicalMappingId(sd); 2666 if (id == null) 2667 return null; 2668 String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); 2669 String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); 2670 if (prefix == null || suffix == null) 2671 return null; 2672 // we build this by text. Any element that has a mapping, we put it's mappings inside it.... 2673 StringBuilder b = new StringBuilder(); 2674 b.append(prefix); 2675 2676 ElementDefinition root = sd.getSnapshot().getElementFirstRep(); 2677 String m = getMapping(root, id); 2678 if (m != null) 2679 b.append(m + "\r\n"); 2680 addChildMappings(b, id, "", sd, root, false); 2681 b.append("\r\n"); 2682 b.append(suffix); 2683 b.append("\r\n"); 2684 StructureMap map = parse(b.toString(), sd.getUrl()); 2685 map.setId(tail(map.getUrl())); 2686 if (!map.hasStatus()) { 2687 map.setStatus(PublicationStatus.DRAFT); 2688 } 2689 if (!map.hasDescription() && map.hasTitle()) { 2690 map.setDescription(map.getTitle()); 2691 } 2692 map.getText().setStatus(NarrativeStatus.GENERATED); 2693 map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); 2694 map.getText().getDiv().addTag("pre").addText(render(map)); 2695 return map; 2696 } 2697 2698 2699 private String tail(String url) { 2700 return url.substring(url.lastIndexOf("/") + 1); 2701 } 2702 2703 2704 private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { 2705 boolean first = true; 2706 List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed, true).getList(); 2707 for (ElementDefinition child : children) { 2708 if (first && inner) { 2709 b.append(" then {\r\n"); 2710 first = false; 2711 } 2712 String map = getMapping(child, id); 2713 if (map != null) { 2714 b.append(indent + " " + child.getPath() + ": " + map); 2715 addChildMappings(b, id, indent + " ", sd, child, true); 2716 b.append("\r\n"); 2717 } 2718 } 2719 if (!first && inner) 2720 b.append(indent + "}"); 2721 2722 } 2723 2724 2725 private String getMapping(ElementDefinition ed, String id) { 2726 for (ElementDefinitionMappingComponent map : ed.getMapping()) 2727 if (id.equals(map.getIdentity())) 2728 return map.getMap(); 2729 return null; 2730 } 2731 2732 2733 private String getLogicalMappingId(StructureDefinition sd) { 2734 String id = null; 2735 for (StructureDefinitionMappingComponent map : sd.getMapping()) { 2736 if ("http://hl7.org/fhir/logical".equals(map.getUri())) 2737 return map.getIdentity(); 2738 } 2739 return null; 2740 } 2741 2742 public ValidationOptions getTerminologyServiceOptions() { 2743 return terminologyServiceOptions; 2744 } 2745 2746 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 2747 this.terminologyServiceOptions = terminologyServiceOptions; 2748 } 2749 2750 public boolean isExceptionsForChecks() { 2751 return exceptionsForChecks; 2752 } 2753 2754 public void setExceptionsForChecks(boolean exceptionsForChecks) { 2755 this.exceptionsForChecks = exceptionsForChecks; 2756 } 2757 2758 public List<StructureMap> getMapsForUrl(List<StructureMap> maps, String url, StructureMapInputMode mode) { 2759 List<StructureMap> res = new ArrayList<>(); 2760 for (StructureMap map : maps) { 2761 if (mapIsForUrl(map, url, mode)) { 2762 res.add(map); 2763 } 2764 } 2765 return res; 2766 } 2767 2768 private boolean mapIsForUrl(StructureMap map, String url, StructureMapInputMode mode) { 2769 for (StructureMapGroupComponent grp : map.getGroup()) { 2770 if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) { 2771 for (StructureMapGroupInputComponent p : grp.getInput()) { 2772 if (mode == null || mode == p.getMode()) { 2773 String t = resolveInputType(p, map); 2774 if (url.equals(t)) { 2775 return true; 2776 } 2777 } 2778 } 2779 } 2780 } 2781 return false; 2782 } 2783 2784 public List<StructureMap> getMapsForUrlPrefix(List<StructureMap> maps, String url, StructureMapInputMode mode) { 2785 List<StructureMap> res = new ArrayList<>(); 2786 for (StructureMap map : maps) { 2787 if (mapIsForUrlPrefix(map, url, mode)) { 2788 res.add(map); 2789 } 2790 } 2791 return res; 2792 } 2793 2794 private boolean mapIsForUrlPrefix(StructureMap map, String url, StructureMapInputMode mode) { 2795 for (StructureMapGroupComponent grp : map.getGroup()) { 2796 if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) { 2797 for (StructureMapGroupInputComponent p : grp.getInput()) { 2798 if (mode == null || mode == p.getMode()) { 2799 String t = resolveInputType(p, map); 2800 if (t != null && t.startsWith(url)) { 2801 return true; 2802 } 2803 } 2804 } 2805 } 2806 } 2807 return false; 2808 } 2809 2810 private String resolveInputType(StructureMapGroupInputComponent p, StructureMap map) { 2811 for (StructureMapStructureComponent struc : map.getStructure()) { 2812 if (struc.hasAlias() && struc.getAlias().equals(p.getType())) { 2813 return struc.getUrl(); 2814 } 2815 } 2816 return null; 2817 } 2818 2819 public ResolvedGroup getGroupForUrl(StructureMap map, String url, StructureMapInputMode mode) { 2820 for (StructureMapGroupComponent grp : map.getGroup()) { 2821 if (grp.getTypeMode() != StructureMapGroupTypeMode.NULL) { 2822 for (StructureMapGroupInputComponent p : grp.getInput()) { 2823 if (mode == null || mode == p.getMode()) { 2824 String t = resolveInputType(p, map); 2825 if (url.equals(t)) { 2826 return new ResolvedGroup(map, grp); 2827 } 2828 } 2829 } 2830 } 2831 } 2832 return null; 2833 } 2834 2835 public String getInputType(ResolvedGroup grp, StructureMapInputMode mode) { 2836 if (grp.getTargetGroup().getInput().size() != 2 || grp.getTargetGroup().getInput().get(0).getMode() == grp.getTargetGroup().getInput().get(1).getMode()) { 2837 return null; 2838 } else if (grp.getTargetGroup().getInput().get(0).getMode() == mode) { 2839 return resolveInputType(grp.getTargetGroup().getInput().get(0), grp.getTargetMap()); 2840 } else { 2841 return resolveInputType(grp.getTargetGroup().getInput().get(1), grp.getTargetMap()); 2842 } 2843 } 2844 2845 public boolean isDebug() { 2846 return debug; 2847 } 2848 2849 public void setDebug(boolean debug) { 2850 this.debug = debug; 2851 } 2852 2853}