001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.HashMap;
006import java.util.List;
007
008import org.hl7.fhir.exceptions.DefinitionException;
009import org.hl7.fhir.exceptions.FHIRFormatError;
010import org.hl7.fhir.r5.conformance.profile.BindingResolution;
011import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
012import org.hl7.fhir.r5.model.*;
013import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
014import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution;
015import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
016import org.hl7.fhir.r5.renderers.utils.RenderingContext;
017import org.hl7.fhir.r5.utils.UserDataNames;
018import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
019import org.hl7.fhir.utilities.Utilities;
020import org.hl7.fhir.utilities.VersionUtilities;
021import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
022import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
023import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
024import org.hl7.fhir.utilities.xhtml.NodeType;
025import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
026import org.hl7.fhir.utilities.xhtml.XhtmlNode;
027import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
028
029@MarkedToMoveToAdjunctPackage
030public class AdditionalBindingsRenderer {
031  public class AdditionalBindingDetail {
032    private String purpose;
033    private String valueSet;
034    private String doco;
035    private String docoShort;
036    private List<UsageContext> usages = new ArrayList<UsageContext>();
037    private boolean any = false;
038    private boolean isUnchanged = false;
039    private boolean matched = false;
040    private boolean removed = false;
041//    private ValueSet vs;
042    
043    private AdditionalBindingDetail compare;
044    private int count = 1;
045    private String getKey() {
046      // Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating
047      return purpose + Integer.toString(count);
048    }
049    private void incrementCount() {
050      count++;
051    }
052    private void setCompare(AdditionalBindingDetail match) {
053      compare = match;
054      match.matched = true;
055    }
056    private boolean alreadyMatched() {
057      return matched;
058    }
059    public String getDoco(boolean full) {
060      return full ? doco : docoShort;
061    }
062    public boolean unchanged() {
063      if (!isUnchanged)
064        return false;
065      if (compare==null)
066        return true;
067      isUnchanged = true;
068      isUnchanged = isUnchanged && ((purpose==null && compare.purpose==null) || purpose.equals(compare.purpose));
069      isUnchanged = isUnchanged && ((valueSet==null && compare.valueSet==null) || valueSet.equals(compare.valueSet));
070      isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco));
071      isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort));
072      isUnchanged = isUnchanged && ((usages==null && compare.usages==null) || usages.equals(compare.usages));
073      return isUnchanged;
074    }
075  }
076
077  private static String STYLE_UNCHANGED = "opacity: 0.5;";
078  private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;";
079
080  private List<AdditionalBindingDetail> bindings = new ArrayList<>();
081  private ProfileKnowledgeProvider pkp;
082  private String corePath;
083  private StructureDefinition profile;
084  private String path;
085  private RenderingContext context;
086  private IMarkdownProcessor md;
087  private CodeResolver cr;
088
089  public AdditionalBindingsRenderer(ProfileKnowledgeProvider pkp, String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr) {
090    this.pkp = pkp;
091    this.corePath = corePath;
092    this.profile = profile;
093    this.path = path;
094    this.context = context;
095    this.md = md;
096    this.cr = cr;
097  }
098
099  public void seeMaxBinding(Extension ext) {
100    seeMaxBinding(ext, null, false);
101  }
102
103  public void seeMaxBinding(Extension ext, Extension compExt, boolean compare) {
104    seeBinding(ext, compExt, compare, "maximum");
105  }
106
107  protected void seeBinding(Extension ext, Extension compExt, boolean compare, String label) {
108    AdditionalBindingDetail abr = new AdditionalBindingDetail();
109    abr.purpose =  label;
110    abr.valueSet =  ext.getValue().primitiveValue();
111    if (compare) {
112      abr.isUnchanged = compExt!=null && ext.getValue().primitiveValue().equals(compExt.getValue().primitiveValue());
113
114      abr.compare = new AdditionalBindingDetail();
115      abr.compare.valueSet = compExt==null ? null : compExt.getValue().primitiveValue();
116    } else {
117      abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
118    }
119    bindings.add(abr);
120  }
121
122  public void seeMinBinding(Extension ext) {
123    seeMinBinding(ext, null, false);
124  }
125
126  public void seeMinBinding(Extension ext, Extension compExt, boolean compare) {
127    seeBinding(ext, compExt, compare, "minimum");
128  }
129
130  public void seeAdditionalBindings(List<Extension> list) {
131    seeAdditionalBindings(list, null, false);
132  }
133
134  public void seeAdditionalBindings(List<Extension> list, List<Extension> compList, boolean compare) {
135    HashMap<String, AdditionalBindingDetail> compBindings = new HashMap<String, AdditionalBindingDetail>();
136    if (compare && compList!=null) {
137      for (Extension ext : compList) {
138        AdditionalBindingDetail abr = additionalBinding(ext);
139        if (compBindings.containsKey(abr.getKey())) {
140          abr.incrementCount();
141        }
142        compBindings.put(abr.getKey(), abr);
143      }
144    }
145
146    for (Extension ext : list) {
147      AdditionalBindingDetail abr = additionalBinding(ext);
148      if (compare && compList!=null) {
149        AdditionalBindingDetail match = null;
150        do {
151          match = compBindings.get(abr.getKey());
152          if (abr.alreadyMatched())
153            abr.incrementCount();
154        } while (match!=null && abr.alreadyMatched());
155        if (match!=null)
156          abr.setCompare(match);
157        bindings.add(abr);
158        if (abr.compare!=null)
159          compBindings.remove(abr.compare.getKey());
160      } else
161        bindings.add(abr);
162    }
163    for (AdditionalBindingDetail b: compBindings.values()) {
164      b.removed = true;
165      bindings.add(b);
166    }
167  }
168
169  protected AdditionalBindingDetail additionalBinding(Extension ext) {
170    AdditionalBindingDetail abr = new AdditionalBindingDetail();
171    abr.purpose =  ext.getExtensionString("purpose");
172    abr.valueSet =  ext.getExtensionString("valueSet");
173    abr.doco =  ext.getExtensionString("documentation");
174    abr.docoShort =  ext.getExtensionString("shortDoco");
175    for (Extension x : ext.getExtensionsByUrl("usage")) {
176      if (x.hasValueUsageContext()) {
177        abr.usages.add(x.getValueUsageContext());
178      }
179    }
180    abr.any = "any".equals(ext.getExtensionString("scope"));
181    abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
182    return abr;
183  }
184
185  protected AdditionalBindingDetail additionalBinding(ElementDefinitionBindingAdditionalComponent ab) {
186    AdditionalBindingDetail abr = new AdditionalBindingDetail();
187    abr.purpose =  ab.getPurpose().toCode();
188    abr.valueSet =  ab.getValueSet();
189    abr.doco =  ab.getDocumentation();
190    abr.docoShort =  ab.getShortDoco();
191    abr.usages.addAll(ab.getUsage());
192    abr.any = ab.getAny();
193    abr.isUnchanged = ab.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
194    return abr;
195  }
196
197  public String render() throws IOException {
198    if (bindings.isEmpty()) {
199      return "";
200    } else {
201      XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table");
202      tbl.attribute("class", "grid");
203      render(tbl.getChildNodes(), true);
204      return new XhtmlComposer(false).compose(tbl);
205    }
206  }
207
208  public void render(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException {
209    if (bindings.isEmpty()) {
210      return;
211    } else {
212      Piece piece = gen.new Piece("binding", "table").setClass("grid");
213      c.getPieces().add(piece);
214      render(piece.getChildren(), false);
215    }
216  }
217  
218  public void render(List<XhtmlNode> children, boolean fullDoco) throws FHIRFormatError, DefinitionException, IOException {
219    boolean doco = false;
220    boolean usage = false;
221    boolean any = false;
222    for (AdditionalBindingDetail binding : bindings) {
223      doco = doco || binding.getDoco(fullDoco)!=null  || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null);
224      usage = usage || !binding.usages.isEmpty() || (binding.compare!=null && !binding.compare.usages.isEmpty());
225      any = any || binding.any || (binding.compare!=null && binding.compare.any);
226    }
227
228    XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr");
229    children.add(tr);
230    tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.ADD_BIND_ADD_BIND));
231    tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_PURPOSE));
232    if (usage) {
233      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_USAGE));
234    }
235    if (any) {
236      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY));
237    }
238    if (doco) {
239      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
240    }
241    for (AdditionalBindingDetail binding : bindings) {
242      tr =  new XhtmlNode(NodeType.Element, "tr");
243      if (binding.unchanged()) {
244        tr.style(STYLE_REMOVED);
245      } else if (binding.removed) {
246        tr.style(STYLE_REMOVED);
247      }
248      children.add(tr);
249      BindingResolution br = pkp == null ? makeNullBr(binding) : pkp.resolveBinding(profile, binding.valueSet, path);
250      BindingResolution compBr = null;
251      if (binding.compare!=null  && binding.compare.valueSet!=null)
252        compBr = pkp == null ? makeNullBr(binding.compare) : pkp.resolveBinding(profile, binding.compare.valueSet, path);
253
254      XhtmlNode valueset = tr.td().style("font-size: 11px");
255      if (binding.compare!=null && binding.valueSet.equals(binding.compare.valueSet))
256        valueset.style(STYLE_UNCHANGED);
257      if (br.url != null) {
258        XhtmlNode a = valueset.ah(context.prefixLocalHref(determineUrl(br.url)), br.uri);
259        a.tx(br.display);
260        if (br.external) {
261          a.tx(" ");
262          a.img("external.png", null);
263        }
264      } else {
265        valueset.span(null, binding.valueSet).tx(br.display);
266      }
267      if (binding.compare!=null && binding.compare.valueSet!=null && !binding.valueSet.equals(binding.compare.valueSet)) {
268        valueset.br();
269        valueset = valueset.span(STYLE_REMOVED, null);
270        if (compBr.url != null) {
271          valueset.ah(context.prefixLocalHref(determineUrl(compBr.url)), binding.compare.valueSet).tx(compBr.display);
272        } else {
273          valueset.span(null, binding.compare.valueSet).tx(compBr.display);
274        }
275      }
276
277      XhtmlNode purpose = tr.td().style("font-size: 11px");
278      if (binding.compare!=null && binding.purpose.equals(binding.compare.purpose))
279        purpose.style("font-color: darkgray");
280      renderPurpose(purpose, binding.purpose);
281      if (binding.compare!=null && binding.compare.purpose!=null && !binding.purpose.equals(binding.compare.purpose)) {
282        purpose.br();
283        purpose = purpose.span(STYLE_UNCHANGED, null);
284        renderPurpose(purpose, binding.compare.purpose);
285      }
286      if (usage) {
287        if (!binding.usages.isEmpty()) {
288          XhtmlNode td = tr.td();
289          for (UsageContext uc : binding.usages) {
290            td.sep(", ");
291            Coding c = uc.getCode();
292            renderUsageCode(td, c);
293            td.tx(" = ");
294            if (uc.hasValueCodeableConcept() && !uc.getValueCodeableConcept().hasText() && uc.getValueCodeableConcept().getCoding().size() == 1) {
295              c = uc.getValueCodeableConcept().getCodingFirstRep();
296              renderUsageCode(td, c);
297            } else if (uc.getValue() != null) {
298              new DataRenderer(context).renderBase(new RenderingStatus(), td, uc.getValue());
299            }
300          }
301        } else {
302          tr.td();          
303        }
304      }
305      if (any) {
306        String newRepeat = binding.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP);
307        String oldRepeat = binding.compare!=null && binding.compare.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP);
308        compareString(tr.td().style("font-size: 11px"), newRepeat, oldRepeat);
309      }
310      if (doco) {
311        if (binding.doco != null) {
312          String d = fullDoco ? md.processMarkdown("Binding.description", binding.doco) : binding.docoShort;
313          String oldD = binding.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", binding.compare.doco) : binding.compare.docoShort;
314          tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD));
315        } else {
316          tr.td().style("font-size: 11px");
317        }
318      }
319    }
320  }
321
322  private void renderUsageCode(XhtmlNode td, Coding c) throws IOException {
323    boolean rendered = false;
324    if (!c.hasDisplay()) {
325      if (c.hasSystem() && c.getSystem().contains("/StructureDefinition/")) {
326        StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, c.getSystem());
327        if (sd != null && sd.hasName()) {
328          rendered = true;
329          td.ah(sd.getWebPath()).tx(sd.getName());
330          td.tx("#");
331          td.code().tx(c.getCode());
332        }
333      } else {
334        CodeSystem cs = context.getContext().fetchCodeSystem(c.getSystem());
335        if (cs != null && cs.hasName()) {
336          rendered = true;
337          td.ah(cs.getWebPath()).tx(cs.getName());
338          td.tx("#");
339          td.code().tx(c.getCode());
340        }
341      }
342    }
343    if (!rendered) {
344      new DataRenderer(context).renderBase(new RenderingStatus(), td, c);
345    }
346  }
347
348  private XhtmlNode compareString(XhtmlNode node, String newS, String oldS) {
349    if (oldS==null)
350      return node.tx(newS);
351    if (newS.equals(oldS))
352      return node.style(STYLE_UNCHANGED).tx(newS);
353    node.tx(newS);
354    node.br();
355    return node.span(STYLE_REMOVED,null).tx(oldS);
356  }
357
358  private String compareHtml(String newS, String oldS) {
359    if (oldS==null)
360      return newS;
361    if (newS.equals(oldS))
362      return "<span style=\"" + STYLE_UNCHANGED + "\">" + newS + "</span>";
363    return newS + "<br/><span style=\"" + STYLE_REMOVED + "\">" + oldS + "</span>";
364  }
365
366  private String determineUrl(String url) {
367    return Utilities.isAbsoluteUrl(url) || !pkp.prependLinks() ? url : corePath + url;
368  }
369
370  private void renderPurpose(XhtmlNode td, String purpose) {
371    boolean r5 = context == null || context.getWorker() == null ? false : VersionUtilities.isR5Plus(context.getWorker().getVersion());
372    switch (purpose) {
373    case "maximum": 
374      td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-maximum" : corePath+"extension-elementdefinition-maxvalueset.html", context.formatPhrase(RenderingContext.ADD_BIND_EXT_PREF)).tx(context.formatPhrase(RenderingContext.ADD_BIND_MAX));
375      break;
376    case "minimum": 
377      td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-minimum" : corePath+"extension-elementdefinition-minvalueset.html", context.formatPhrase(RenderingContext.GENERAL_BIND_MIN_ALLOW)).tx(context.formatPhrase(RenderingContext.ADD_BIND_MIN));
378      break;
379    case "required" :
380      td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-required" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_VALID_REQ)).tx(context.formatPhrase(RenderingContext.ADD_BIND_REQ_BIND));
381      break;
382    case "extensible" :
383      td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-extensible" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_VALID_EXT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_EX_BIND));
384      break;
385    case "preferred" :
386      td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-preferred" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_RECOM_VALUE_SET)).tx(context.formatPhrase(RenderingContext.ADD_BIND_PREF_BIND));
387      break;
388    case "current" :
389      if (r5) {
390        td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-current", context.formatPhrase(RenderingContext.ADD_BIND_NEW_REC)).tx(context.formatPhrase(RenderingContext.ADD_BIND_CURR_BIND));
391      } else {
392        td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_NEW_REC)).tx(context.formatPhrase(RenderingContext.ADD_BIND_CURR_BIND));
393      }
394      break;
395    case "ui" :
396      if (r5) {
397        td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-ui", context.formatPhrase(RenderingContext.ADD_BIND_GIVEN_CONT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_UI_BIND));
398      } else {
399        td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_GIVEN_CONT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_UI));        
400      }
401      break;
402    case "starter" :
403      if (r5) {
404        td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-starter",  context.formatPhrase(RenderingContext.ADD_BIND_DESIG_SYS)).tx(context.formatPhrase(RenderingContext.GENERAL_STARTER));
405      } else {
406        td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_DESIG_SYS)).tx(context.formatPhrase(RenderingContext.GENERAL_STARTER));        
407      }
408      break;
409    case "component" :
410      if (r5) {
411        td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-component", context.formatPhrase(RenderingContext.ADD_BIND_VALUE_COMP)).tx(context.formatPhrase(RenderingContext.GENERAL_COMPONENT));
412      } else {
413        td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_VALUE_COMP)).tx(context.formatPhrase(RenderingContext.GENERAL_COMPONENT));        
414      }
415      break;
416    default:  
417      td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_UNKNOWN_PUR)).tx(purpose);
418    }
419  }
420
421  private BindingResolution makeNullBr(AdditionalBindingDetail binding) {
422    BindingResolution br = new BindingResolution();
423    br.url = "http://none.none/none";
424    br.display = "todo";
425    return br;
426  }
427
428  public boolean hasBindings() {
429    return !bindings.isEmpty();
430  }
431
432  public void render(XhtmlNodeList children, List<ElementDefinitionBindingAdditionalComponent> list) {
433    if (list.size() == 1) {
434      render(children, list.get(0));
435    } else {
436      XhtmlNode ul = children.ul();
437      for (ElementDefinitionBindingAdditionalComponent b : list) {
438        render(ul.li().getChildNodes(), b);
439      }
440    }
441  }
442
443  private void render(XhtmlNodeList children, ElementDefinitionBindingAdditionalComponent b) {
444    if (b.getValueSet() == null) {
445      return; // what should happen?
446    }
447    BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath);
448    XhtmlNode a = children.ahOrCode(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : br.uri);
449    if (b.hasDocumentation()) {
450      a.attribute("title", b.getDocumentation());
451    } 
452    a.tx(br.display);
453
454    if (b.hasShortDoco()) {
455      children.tx(": ");
456      children.tx(b.getShortDoco());
457    } 
458    if (b.getAny() || b.hasUsage()) {
459      children.tx(" (");
460      boolean ffirst = !b.getAny();
461      if (b.getAny()) {
462        children.tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP));
463      }
464      for (UsageContext uc : b.getUsage()) {
465        if (ffirst) ffirst = false; else children.tx(",");
466        if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) {
467          children.tx(displayForUsage(uc.getCode()));
468          children.tx("=");
469        }
470        CodeResolution ccr = cr.resolveCode(uc.getValueCodeableConcept());
471        children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay());
472      }
473      children.tx(")");
474    }
475  }
476
477  
478  private String displayForUsage(Coding c) {
479    if (c.hasDisplay()) {
480      return c.getDisplay();
481    }
482    if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) {
483      return c.getCode();
484    }
485    return c.getCode();
486  }
487
488  public void seeAdditionalBinding(String purpose, String doco, ValueSet valueSet) {
489    AdditionalBindingDetail abr = new AdditionalBindingDetail();
490    abr.purpose =  purpose;
491    abr.valueSet =  valueSet.getUrl();
492    bindings.add(abr);
493  }
494
495  public void seeAdditionalBinding(String purpose, String doco, String ref) {
496    AdditionalBindingDetail abr = new AdditionalBindingDetail();
497    abr.purpose =  purpose;
498    abr.valueSet =  ref;
499    bindings.add(abr);
500    
501  }
502
503  public void seeAdditionalBindings(ElementDefinition definition, ElementDefinition compDef, boolean compare) {
504    HashMap<String, AdditionalBindingDetail> compBindings = new HashMap<String, AdditionalBindingDetail>();
505    if (compare && compDef.getBinding().getAdditional() != null) {
506      for (ElementDefinitionBindingAdditionalComponent ab : compDef.getBinding().getAdditional()) {
507        AdditionalBindingDetail abr = additionalBinding(ab);
508        if (compBindings.containsKey(abr.getKey())) {
509          abr.incrementCount();
510        }
511        compBindings.put(abr.getKey(), abr);
512      }
513    }
514
515    for (ElementDefinitionBindingAdditionalComponent ab : definition.getBinding().getAdditional()) {
516      AdditionalBindingDetail abr = additionalBinding(ab);
517      if (compare && compDef != null) {
518        AdditionalBindingDetail match = null;
519        do {
520          match = compBindings.get(abr.getKey());
521          if (abr.alreadyMatched())
522            abr.incrementCount();
523        } while (match!=null && abr.alreadyMatched());
524        if (match!=null)
525          abr.setCompare(match);
526        bindings.add(abr);
527        if (abr.compare!=null)
528          compBindings.remove(abr.compare.getKey());
529      } else
530        bindings.add(abr);
531    }
532    for (AdditionalBindingDetail b: compBindings.values()) {
533      b.removed = true;
534      bindings.add(b);
535    }
536    
537  }
538
539}