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