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