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