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