001package org.hl7.fhir.r5.context;
002
003import java.util.*;
004
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.r5.model.CanonicalResource;
007import org.hl7.fhir.r5.model.CodeSystem;
008import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
009import org.hl7.fhir.r5.model.PackageInformation;
010import org.hl7.fhir.r5.model.StructureDefinition;
011import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
012import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
013import org.hl7.fhir.utilities.NaturalOrderComparator;
014import org.hl7.fhir.utilities.Utilities;
015import org.hl7.fhir.utilities.VersionUtilities;
016
017/**
018 * This manages a cached list of resources, and provides high speed access by URL / URL+version, and assumes that patch version doesn't matter for access
019 * note, though, that not all resources have semver versions
020 * 
021 * @author graha
022 *
023 */
024
025@MarkedToMoveToAdjunctPackage
026public class CanonicalResourceManager<T extends CanonicalResource> {
027
028  private final String[] INVALID_TERMINOLOGY_URLS = {
029    "http://snomed.info/sct",
030    "http://dicom.nema.org/resources/ontology/DCM",
031    "http://nucc.org/provider-taxonomy"
032  };
033  private int loadCount = 0;
034
035  public static abstract class CanonicalResourceProxy {
036    private String type;
037    private String id;
038    private String url;
039    private String version;
040    private String supplements;
041    private String derivation;
042    private CanonicalResource resource;
043    private boolean hacked;
044    private String content;
045    
046    public CanonicalResourceProxy(String type, String id, String url, String version, String supplements, String derivation, String content) {
047      super();
048      this.type = type;
049      this.id = id;
050      this.url = url;
051      this.version = version;
052      this.supplements = supplements;
053      this.content = content;
054      this.derivation = derivation;
055    }
056    
057    public String getType() {
058      return type;
059    }
060
061    public String getId() {
062      return id;
063    }
064    
065    public String getUrl() {
066      return url;
067    }
068    
069    public String getVersion() {
070      return version;
071    }
072    
073    public boolean hasId() {
074      return id != null;
075    }
076    
077    public boolean hasUrl() {
078      return url != null;
079    }
080    
081    public boolean hasVersion() {
082      return version != null;
083    }
084    
085    public String getSupplements() {
086      return supplements;
087    }
088
089    
090    public String getContent() {
091      return content;
092    }
093
094    public String getDerivation() {
095      return derivation;
096    }
097
098    public void setDerivation(String derivation) {
099      this.derivation = derivation;
100    }
101
102    public CanonicalResource getResource() throws FHIRException {
103      if (resource == null) {
104        resource = loadResource();
105        if (hacked) {
106          resource.setUrl(url).setVersion(version);
107        }
108        if (resource instanceof CodeSystem) {
109          CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) resource);
110        }
111      }
112      return resource;
113    }
114
115    public void setResource(CanonicalResource resource) {
116      this.resource = resource;
117    }
118
119    public abstract CanonicalResource loadResource() throws FHIRException;
120
121    @Override
122    public String toString() {
123      return type+"/"+id+": "+url+"|"+version;
124    }
125
126    public void hack(String url, String version) {
127      this.url = url;
128      this.version = version;
129      this.hacked = true;
130
131    }
132    
133    /** 
134     * used in cross version settings by the package loaders.
135     */
136    public void updateInfo() {
137      type = resource.fhirType();
138      id = resource.getId();
139      url = resource.getUrl();
140      version = resource.getVersion();
141      if (resource instanceof CodeSystem) {
142        supplements = ((CodeSystem) resource).getSupplements();
143        content = ((CodeSystem) resource).getContentElement().asStringValue();
144      }
145      if (resource instanceof StructureDefinition) {
146        derivation = ((StructureDefinition) resource).getDerivationElement().asStringValue();
147      }
148    }    
149  }
150
151  public static class CanonicalListSorter implements Comparator<CanonicalResource> {
152
153    @Override
154    public int compare(CanonicalResource arg0, CanonicalResource arg1) {
155      String u0 = arg0.getUrl();
156      String u1 = arg1.getUrl();
157      return u0.compareTo(u1);
158    }
159  }
160
161  public class CachedCanonicalResource<T1 extends CanonicalResource> {
162    int loadingOrder = 0;
163    private T1 resource;
164    private CanonicalResourceProxy proxy;
165    private PackageInformation packageInfo;
166
167    public CachedCanonicalResource(T1 resource, PackageInformation packageInfo) {
168      super();
169      if (resource == null) {
170        throw new NullPointerException("Canonical resource cannot be null");
171      }
172      this.resource = resource;
173      this.packageInfo = packageInfo;
174    }
175    
176    public CachedCanonicalResource(CanonicalResourceProxy proxy, PackageInformation packageInfo) {
177      super();
178      if (proxy == null) {
179        throw new NullPointerException("Canonical resource proxy cannot be null");
180      }
181      this.proxy = proxy;
182      this.packageInfo = packageInfo;
183    }
184    
185    public T1 getResource() {
186      if (resource == null) {
187        @SuppressWarnings("unchecked")
188        T1 res = (T1) proxy.getResource();
189        if (res == null) {
190          throw new Error("Proxy loading a resource from "+packageInfo+" failed and returned null");
191        }
192        synchronized (this) {
193          resource = res;
194        }
195        resource.setSourcePackage(packageInfo);
196        proxy = null;
197      }
198      return resource;
199    }
200    
201    public PackageInformation getPackageInfo() {
202      return packageInfo;
203    }
204    public String getUrl() {
205      return resource != null ? resource.getUrl() : proxy.getUrl();
206    }
207    public String getId() {
208      return resource != null ? resource.getId() : proxy.getId();
209    }
210    public String getVersion() {
211      return resource != null ? resource.getVersion() : proxy.getVersion();
212    }
213    public boolean hasVersion() {
214      return resource != null ? resource.hasVersion() : proxy.getVersion() != null;
215    }
216    public String getContent() {
217      if (this.resource instanceof CodeSystem) {
218        CodeSystemContentMode cnt = ((CodeSystem) resource).getContent();
219        return cnt == null ? null : cnt.toCode();
220      } else if (proxy != null) {
221        return proxy.getContent();
222      } else {
223        return null;
224      }
225    }
226    
227    @Override
228    public String toString() {
229      return resource != null ? resource.fhirType()+"/"+resource.getId()+"["+resource.getUrl()+"|"+resource.getVersion()+"]" : proxy.toString();
230    }
231
232    public String supplements() {
233      if (resource == null) {
234        return proxy.getSupplements(); 
235      } else {
236        return resource instanceof CodeSystem ? ((CodeSystem) resource).getSupplements() : null;
237      }
238    }
239
240    public Object getDerivation() {
241      if (resource == null) {
242        return proxy.getDerivation(); 
243      } else {
244        return resource instanceof StructureDefinition ? ((StructureDefinition) resource).getDerivationElement().primitiveValue() : null;
245      }
246    }
247
248    public void unload() {
249      if (proxy != null) {
250        resource = null;
251      }      
252    }
253
254    public int getLoadingOrder() {
255      return loadingOrder;
256    }
257
258    public void setLoadingOrder(int loadingOrder) {
259      this.loadingOrder = loadingOrder;
260    }
261  }
262
263  public class MetadataResourceVersionComparator<T1 extends CachedCanonicalResource<T>> implements Comparator<T1> {
264    @Override
265    public int compare(T1 arg1, T1 arg2) {
266      return compareResources(arg1, arg2);
267    }
268
269  }
270
271  private boolean minimalMemory;
272  private boolean enforceUniqueId; 
273  private List<CachedCanonicalResource<T>> list = new ArrayList<>();
274  private Map<String, List<CachedCanonicalResource<T>>> listForId;
275  private Map<String, List<CachedCanonicalResource<T>>> listForUrl;
276  private Map<String, CachedCanonicalResource<T>> map;
277  private Map<String, List<CachedCanonicalResource<T>>> supplements; // general index based on CodeSystem.supplements
278  private String version; // for debugging purposes
279  
280  
281  public CanonicalResourceManager(boolean enforceUniqueId, boolean minimalMemory) {
282    super();
283    this.enforceUniqueId = enforceUniqueId;
284    this.minimalMemory = minimalMemory;
285    list = new ArrayList<>();
286    listForId = new HashMap<>();
287    listForUrl = new HashMap<>();
288    map = new HashMap<>();
289    supplements = new HashMap<>(); // general index based on CodeSystem.supplements
290  }
291
292  
293  public String getVersion() {
294    return version;
295  }
296
297
298  public void setVersion(String version) {
299    this.version = version;
300  }
301
302
303  public void copy(CanonicalResourceManager<T> source) {
304    list.clear();
305    map.clear();
306    list.addAll(source.list);
307    map.putAll(source.map);
308  }
309  
310  public void register(CanonicalResourceProxy r, PackageInformation packgeInfo) {
311    if (!r.hasId()) {
312      throw new FHIRException("An id is required for a deferred load resource");
313    }
314    CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo);
315    see(cr);
316  }
317
318  public void see(T r, PackageInformation packgeInfo) {
319    if (r != null) {
320      if (!r.hasId()) {
321        r.setId(UUID.randomUUID().toString());
322      }
323      CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo);
324      see(cr);
325    }
326  }
327
328  public void see(CachedCanonicalResource<T> cr) {
329    // -- 1. exit conditions -----------------------------------------------------------------------------
330
331    // ignore UTG NUCC erroneous code system
332    if (cr.getPackageInfo() != null
333      && cr.getPackageInfo().getId() != null
334      && cr.getPackageInfo().getId().startsWith("hl7.terminology")
335      && Arrays.stream(INVALID_TERMINOLOGY_URLS).anyMatch((it)->it.equals(cr.getUrl()))) {
336      return;
337    }  
338    if (map.get(cr.getUrl()) != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) {
339      return;
340    }
341
342    // -- 2. preparation -----------------------------------------------------------------------------
343    if (cr.resource != null && cr.getPackageInfo() != null) {
344      cr.resource.setSourcePackage(cr.getPackageInfo());
345    }      
346
347    // -- 3. deleting existing content ---------------------------------------------------------------
348    if (enforceUniqueId && map.containsKey(cr.getId())) {
349      drop(cr.getId());      
350    }
351    
352    // special case logic for UTG support prior to version 5
353    if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) {
354      List<CachedCanonicalResource<T>> toDrop = new ArrayList<>();
355      for (CachedCanonicalResource<T> n : list) {
356        if (n.getUrl() != null && n.getUrl().equals(cr.getUrl()) && isBasePackage(n.getPackageInfo())) {
357          toDrop.add(n);
358        }
359      }
360      for (CachedCanonicalResource<T> n : toDrop) {
361        drop(n);
362      }
363    }
364//    CachedCanonicalResource<T> existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0");
365//    if (existing != null) {
366//      drop(existing); // was list.remove(existing)
367//    }
368    
369    // -- 4. ok we add it to the list ---------------------------------------------------------------
370    if (!enforceUniqueId) {
371      if (!listForId.containsKey(cr.getId())) {
372        listForId.put(cr.getId(), new ArrayList<>());
373      }    
374      List<CachedCanonicalResource<T>> set = listForId.get(cr.getId());
375      set.add(cr);      
376    }
377    list.add(cr);
378    if (!listForUrl.containsKey(cr.getUrl())) {
379      listForUrl.put(cr.getUrl(), new ArrayList<>());
380    }    
381    addToSupplements(cr);
382    List<CachedCanonicalResource<T>> set = listForUrl.get(cr.getUrl());
383    set.add(cr);
384    if (set.size() > 1) {
385      Collections.sort(set, new MetadataResourceVersionComparator<CachedCanonicalResource<T>>());
386    }
387
388    // -- 4. add to the map all the ways ---------------------------------------------------------------
389    String pv = cr.getPackageInfo() != null ? cr.getPackageInfo().getVID() : null;
390    addToMap(cr.getId(), cr); // we do this so we can drop by id - if not enforcing id, it's just the most recent resource with this id
391    addToMap(cr.hasVersion() ? cr.getUrl()+"|"+cr.getVersion() : cr.getUrl()+"|#0", cr);
392    if (pv != null) {
393      addToMap(pv+":"+(cr.hasVersion() ? cr.getUrl()+"|"+cr.getVersion() : cr.getUrl()+"|#0"), cr);
394    }
395    int ndx = set.indexOf(cr);
396    if (ndx == set.size()-1) {
397      addToMap(cr.getUrl(), cr);
398      if (pv != null) {
399        addToMap(pv+":"+cr.getUrl(), cr);
400      }
401    }
402    String mmp = VersionUtilities.getMajMinPatch(cr.getVersion());
403    if (mmp != null && !mmp.equals(cr.getVersion())) {
404      if (pv != null) {
405        addToMap(pv+":"+cr.getUrl()+"|"+mmp, cr);
406      }
407      if (set.size() - 1 == ndx) {
408        addToMap(cr.getUrl()+"|"+mmp, cr);
409      } else {
410        for (int i = set.size() - 1; i > ndx; i--) {
411          if (mmp.equals(VersionUtilities.getMajMinPatch(set.get(i).getVersion()))) {
412            return;
413          }
414          addToMap(cr.getUrl()+"|"+mmp, cr);
415        }
416      }
417    }
418
419    String mm = VersionUtilities.getMajMin(cr.getVersion());
420    if (mm != null) {
421      if (pv != null) {
422        addToMap(pv+":"+cr.getUrl()+"|"+mm, cr);
423      }
424      if (set.size() - 1 == ndx) {
425        addToMap(cr.getUrl()+"|"+mm, cr);
426      } else {
427        for (int i = set.size() - 1; i > ndx; i--) {
428          if (mm.equals(VersionUtilities.getMajMin(set.get(i).getVersion()))) {
429            return;
430          }
431          addToMap(cr.getUrl()+"|"+mm, cr);
432        }
433      }
434    }
435    cr.setLoadingOrder(++loadCount);
436  }
437
438  private int compareResources (CachedCanonicalResource<T> c1, CachedCanonicalResource<T> c2) {
439    // the order of comparison is
440    // by resource version
441    // by content type (ignore content=not-present code systems)
442    // by package version (e.g for THO)
443    // by whether we've seen it or not
444    int res = compareByResourceVersion(c1.getVersion(), c2.getVersion());
445    if (res == 0) {
446      res = compareByResourceContent(c1.getContent(), c2.getContent());
447    }
448    if (res == 0) {
449      res = compareByPackageVersion(c1.getPackageInfo(), c2.getPackageInfo());
450    }
451    if (res == 0) {
452      res = compareByLoadCount(c1.getLoadingOrder(), c2.getLoadingOrder());
453    }
454    return res;
455  }
456
457  private int compareByLoadCount(int o1, int o2) {
458    if (01 == 0 && o2 == 0) {
459      return 0;
460    } else if (o1 == 0) {
461      return 1;
462    } else if (o2 == 0) {
463      return -1;
464    } else {
465      return Integer.compare(o1, o2);
466    }
467  }
468
469  private int compareByResourceVersion(String v1, String v2) {
470    if (v1 == null && v2 == null) {
471      return 0;
472    } else if (v1 == null) {
473      return -1; // this order is deliberate
474    } else if (v2 == null) {
475      return 1;
476    } else if (VersionUtilities.isSemVer(v1) && VersionUtilities.isSemVer(v2)) {
477      return VersionUtilities.compareVersions(v1, v2);
478    } else if (Utilities.isInteger(v1) && Utilities.isInteger(v2)) {
479      return Integer.compare(Integer.parseInt(v1), Integer.parseInt(v2));
480    } else {
481      return new NaturalOrderComparator<String>().compare(v1, v2);
482    }
483  }
484
485  private int compareByLoadCount(String v1, String v2) {
486    if (v1 == null && v2 == null) {
487      return 0;
488    } else if (v1 == null) {
489      return 1;
490    } else if (v2 == null) {
491      return -1;
492    } else if (VersionUtilities.isSemVer(v1) && VersionUtilities.isSemVer(v2)) {
493      return VersionUtilities.compareVersions(v1, v2);
494    } else if (Utilities.isInteger(v1) && Utilities.isInteger(v2)) {
495      return Integer.compare(Integer.parseInt(v1), Integer.parseInt(v2));
496    } else {
497      return new NaturalOrderComparator<String>().compare(v1, v2);
498    }
499  }
500
501  private int compareByPackageVersion(PackageInformation p1, PackageInformation p2) {
502    if (p1 == null && p2 == null) {
503      return 0;
504    } else if (p1 == null) {
505      return 1; // this order is deliberate.
506    } else if (p2 == null) {
507      return -1;
508    } else if (p1.getId().equals(p2.getId())) {
509      int res = compareByResourceVersion(p1.getVersion(), p2.getVersion());
510      if (res == 0) {
511        res = compareByDate(p1.getDate(), p2.getDate());
512      }
513      return res;
514    } else {
515      // comes from a mismatch package - presumable some repackaging has gone on - the most recent by date wins
516      return compareByDate(p1.getDate(), p2.getDate());
517    }
518  }
519
520  private int compareByDate(Date d1, Date d2) {
521    if (d1 == null && d2 == null) {
522      return 0;
523    } else if (d1 == null) {
524      return 1;
525    } else if (d2 == null) {
526      return -1;
527    } else {
528      return d1.compareTo(d2);
529    }
530  }
531
532  private int compareByResourceContent(String c1, String c2) {
533    if (c1 == null && c2 == null) {
534      return 0;
535    } else if (c1 == null) {
536      return -1;
537    } else if (c2 == null) {
538      return 1;
539    } else if (c1.equals(c2)) {
540      return 0;
541    } else {
542      int i1 = orderOfContent(c1);
543      int i2 = orderOfContent(c2);
544      return Integer.compare(i1, i2);
545    }
546  }
547
548  private int orderOfContent(String c) {
549    switch (c) {
550      case "not-present": return 1;
551      case "example": return 2;
552      case "fragment": return 3;
553      case "complete": return 5;
554      case "supplement": return 4;
555    }
556    return 0;
557  }
558
559  private void addToMap(String key, CachedCanonicalResource<T> cr) {
560    boolean newIsOlder = false;
561    CachedCanonicalResource<T> existing = map.get(key);
562    if (existing != null) {
563      // at every level of adding something to the map, the most recent (by versioning) wins.
564      int comp = compareResources(existing, cr);
565      newIsOlder = comp == 1;
566    }
567    if (!newIsOlder) {
568      map.put(key, cr);
569    }
570  }
571
572  private void addToSupplements(CanonicalResourceManager<T>.CachedCanonicalResource<T> cr) {
573    String surl = cr.supplements();
574    if (surl != null) {
575      List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(surl);
576      if (list == null) {
577        list = new ArrayList<>();
578        supplements.put(surl, list);
579      }
580      list.add(cr);
581    }    
582  }
583
584
585  public void drop(CachedCanonicalResource<T> cr) {
586    while (map.values().remove(cr)); 
587    while (listForId.values().remove(cr)); 
588    while (listForUrl.values().remove(cr)); 
589    String surl = cr.supplements();
590    if (surl != null) {
591      supplements.get(surl).remove(cr);
592    }
593    list.remove(cr);
594    List<CachedCanonicalResource<T>> set = listForUrl.get(cr.getUrl());
595    if (set != null) { // it really should be
596      boolean last = set.indexOf(cr) == set.size()-1;
597      set.remove(cr);
598      if (!set.isEmpty()) {
599        CachedCanonicalResource<T> crl = set.get(set.size()-1);
600        if (last) {
601          map.put(crl.getUrl(), crl);
602        }
603        String mm = VersionUtilities.getMajMin(cr.getVersion());
604        if (mm != null) {
605          for (int i = set.size()-1; i >= 0; i--) {
606            if (mm.equals(VersionUtilities.getMajMin(set.get(i).getVersion()))) {
607              map.put(cr.getUrl()+"|"+mm, set.get(i));
608              break;
609            }
610          }
611        }
612      }
613    }
614  }
615  
616  public void drop(String id) {
617    if (enforceUniqueId) {
618      CachedCanonicalResource<T> cr = map.get(id);
619      if (cr != null) {
620        drop(cr);
621      }
622    } else {
623      List<CachedCanonicalResource<T>> set = listForId.get(id);
624      if (set != null) { // it really should be
625        for (CachedCanonicalResource<T> i : set) {
626          drop(i);
627        }
628      }
629    }
630  }  
631
632  private boolean isBasePackage(PackageInformation packageInfo) {
633    return packageInfo == null ? false : VersionUtilities.isCorePackage(packageInfo.getId());
634  }
635
636  private void updateList(String url, String version) {
637    List<CachedCanonicalResource<T>> rl = new ArrayList<>();
638    for (CachedCanonicalResource<T> t : list) {
639      if (url.equals(t.getUrl()) && !rl.contains(t)) {
640        rl.add(t);
641      }
642    }
643    if (rl.size() > 0) {
644      // sort by version as much as we are able
645      // the current is the latest
646      map.put(url, rl.get(rl.size()-1));
647      // now, also, the latest for major/minor
648      if (version != null) {
649        CachedCanonicalResource<T> latest = null;
650        for (CachedCanonicalResource<T> t : rl) {
651          if (VersionUtilities.versionMatches(t.getVersion(), version)) {
652            latest = t;
653          }
654        }
655        if (latest != null) { // might be null if it's not using semver
656          String lv = VersionUtilities.getMajMin(latest.getVersion());
657          if (lv != null && !lv.equals(version))
658            map.put(url+"|"+lv, rl.get(rl.size()-1));
659        }
660      }
661    }
662  }
663 
664
665  public boolean has(String url) {
666    return map.containsKey(url);
667  }
668
669  public boolean has(String system, String version) {
670    if (map.containsKey(system+"|"+version))
671      return true;
672    String mm = VersionUtilities.getMajMin(version);
673    if (mm != null)
674      return map.containsKey(system+"|"+mm);
675    else
676      return false;
677  }
678  
679  public T get(String url) {
680    return map.containsKey(url) ? map.get(url).getResource() : null;
681  }
682  
683  public T get(String system, String version) {
684    if (version == null) {
685      return get(system);
686    } else {
687      if (map.containsKey(system+"|"+version))
688        return map.get(system+"|"+version).getResource();
689      if (VersionUtilities.isSemVer(version) && !Utilities.containsInList(version, "+", "-")) {
690        String mm = VersionUtilities.getMajMin(version);
691        if (mm != null && map.containsKey(system + "|" + mm))
692          return map.get(system + "|" + mm).getResource();
693
694      }
695      return null;
696    }
697  }
698  
699  public List<T> getForUrl(String url) {
700    List<T> res = new ArrayList<>();
701    List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = listForUrl.get(url);
702    if (list != null) {
703      for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
704        res.add(t.getResource());
705      }
706    }
707    return res;
708  }
709  
710  /**
711   * This is asking for a packaged version aware resolution
712   * 
713   * if we can resolve the reference in the package dependencies, we will. if we can't
714   * then we fall back to the non-package approach
715   * 
716   *  The context has to prepare the pvlist based on the original package
717   * @param url
718   * @param srcInfo
719   * @return
720   */
721  public T getByPackage(String url, List<String> pvlist) {
722    for (String pv : pvlist) {
723      if (map.containsKey(pv+":"+url)) {
724        return map.get(pv+":"+url).getResource();
725      }      
726    }
727    return map.containsKey(url) ? map.get(url).getResource() : null;
728  }
729  
730  public T getByPackage(String system, String version, List<String> pvlist) {
731    if (version == null) {
732      return getByPackage(system, pvlist);
733    } else {
734      for (String pv : pvlist) {
735        if (map.containsKey(pv+":"+system+"|"+version))
736          return map.get(pv+":"+system+"|"+version).getResource();
737      }
738      String mm = VersionUtilities.getMajMin(version);
739      if (mm != null && map.containsKey(system+"|"+mm))
740        for (String pv : pvlist) {
741          if (map.containsKey(pv+":"+system+"|"+mm))
742            return map.get(pv+":"+system+"|"+mm).getResource();
743      }
744
745      if (map.containsKey(system+"|"+version))
746        return map.get(system+"|"+version).getResource();
747      if (mm != null && map.containsKey(system+"|"+mm))
748        return map.get(system+"|"+mm).getResource();
749      else
750        return null;
751    }
752  }
753  
754  
755 
756  public PackageInformation getPackageInfo(String system, String version) {
757    if (version == null) {
758      return map.containsKey(system) ? map.get(system).getPackageInfo() : null;
759    } else {
760      if (map.containsKey(system+"|"+version))
761        return map.get(system+"|"+version).getPackageInfo();
762      String mm = VersionUtilities.getMajMin(version);
763      if (mm != null && map.containsKey(system+"|"+mm))
764        return map.get(system+"|"+mm).getPackageInfo();
765      else
766        return null;
767    }
768  }
769  
770 
771  
772  
773  public int size() {
774    return list.size();
775  }
776  
777
778  
779  public void listAll(List<T> result) {
780    for (CachedCanonicalResource<T>  t : list) {
781      result.add(t.getResource()); 
782    }
783  }
784
785  public void listAllM(List<CanonicalResource> result) {
786    for (CachedCanonicalResource<T>  t : list) {
787      result.add(t.getResource()); 
788    }
789  }
790
791  public List<T> getSupplements(T cr) {
792    if (cr == null) {
793      return new ArrayList<T>();
794    }
795    if (cr.hasSourcePackage()) {
796      List<String> pvl = new ArrayList<>();
797      pvl.add(cr.getSourcePackage().getVID());
798      return getSupplements(cr.getUrl(), cr.getVersion(), pvl);    
799    } else {
800      return getSupplements(cr.getUrl(), cr.getVersion(), null);
801    }
802  }
803  
804  public List<T> getSupplements(String url) {
805    return getSupplements(url, null, null);    
806  }
807  
808  public List<T> getSupplements(String url, String version) {
809    return getSupplements(url, version, null);    
810  }
811  
812  public List<T> getSupplements(String url, String version, List<String> pvlist) {
813    boolean possibleMatches = false;
814    List<T> res = new ArrayList<>();
815    if (version != null) {
816      List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(url+"|"+version);
817      if (list != null) {
818        for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
819          possibleMatches = true;
820          if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) {
821            res.add(t.getResource());
822          }
823        }
824      }      
825    }
826    List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(url);
827    if (list != null) {
828      for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
829        possibleMatches = true;
830        if (pvlist == null || t.getPackageInfo() == null || pvlist.contains(t.getPackageInfo().getVID())) {
831          res.add(t.getResource());
832        }
833      }
834    }
835    if (res.isEmpty() && pvlist != null && possibleMatches) {
836      return getSupplements(url, version, null);
837    } else {
838      return res;
839    }
840  }
841  
842  public void clear() {
843    list.clear();
844    map.clear();
845    
846  }
847
848  public List<CachedCanonicalResource<T>> getCachedList() {
849    return list;
850  }
851
852  public List<T> getList() {
853    List<T> res = new ArrayList<>();
854    for (CachedCanonicalResource<T> t : list) {
855      if (!res.contains(t.getResource())) {
856        res.add(t.getResource());
857      }
858    }
859    return res;
860  }
861
862  public List<T> getVersionList(String url) {
863    List<T> res = new ArrayList<>();
864    for (CachedCanonicalResource<T> t : list) {
865      if (url.equals(t.getUrl())) {
866        if (!res.contains(t.getResource())) {
867          res.add(t.getResource());
868        }
869
870      }
871    }
872    return res;
873  }
874
875  public List<T> getSortedList() {
876    List<T> res = getList();
877    Collections.sort(res, new CanonicalListSorter());
878    return res;
879  }
880
881  public Set<String> keys() {
882    return map.keySet();
883  }
884
885  public boolean isEnforceUniqueId() {
886    return enforceUniqueId;
887  }
888
889
890  public void unload() {
891    for (CachedCanonicalResource<T> t : list) {
892      t.unload();
893    }
894   
895  }
896
897
898}