/*- * Copyright 2015 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package uk.ac.diamond.scisoft.analysis.io.cache; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import org.eclipse.dawnsci.analysis.api.io.IDataHolder; /** * Class to encapsulate data caching for LoaderFactory */ public class DataCache<T> { private static final String NO_CACHING = "uk.ac.diamond.scisoft.analysis.io.nocaching"; // used when we are caching items do not have an image number private static final int NOT_A_SINGLE_IMAGE = -1; /** * A caching mechanism using soft references. Soft references attempt to keep things * in memory until the system is short on memory. Hashtable used because it is synchronized * which should reduce chances of getting the wrong data for the key. */ private final Map<CacheKey, Reference<T>> cache; public DataCache() { cache = new Hashtable<CacheKey, Reference<T>>(89); } /** * Used for synchronization. */ private final static Object LOCK = new Object(); public void clear() { cache.clear(); } public void clear(String filePath) { for (Iterator<CacheKey> it = cache.keySet().iterator();it.hasNext();) { CacheKey key = it.next(); if (filePath.equals(key.getFilePath())) it.remove(); } } public CacheKey createCacheKey(String path, boolean loadMeta) { final CacheKey key = new CacheKey(); key.setFilePath(path); key.setMetadata(loadMeta); key.setImageNumber(NOT_A_SINGLE_IMAGE); return key; } /** * May be null * @param key * @return the object referenced or null if it got garbaged or was not cached yet */ public T getSoftReference(CacheKey key) { T o = getReference(key); if (o != null) { return o; } if (key.hasMetadata()) { // wanted metadata but none there return null; } key.setMetadata(true); // try with unwanted metadata return getReference(key); } /** * May be null * @param key * @return the object referenced or null if it got garbaged or was not cached yet */ public T getSoftReferenceWithMetadata(CacheKey key) { T o = getReference(key); if (o != null) return o; CacheKey k = findKeyWithMetadata(key); return k == null ? null : getReference(k); } /** * May be null * @param key * @return the object referenced or null if it got garbaged or was not cached yet */ private T getReference(CacheKey key) { if (Boolean.getBoolean(NO_CACHING)) return null; synchronized (LOCK) { try { final Reference<T> ref = cache.get(key); if (ref == null) return null; T got = ref.get(); return got; } catch (Throwable ne) { return null; } } } private CacheKey findKeyWithMetadata(CacheKey key) { if (Boolean.getBoolean(NO_CACHING)) return null; synchronized (LOCK) { for (CacheKey k : cache.keySet()) { if (k.isSameFile(key) && k.hasMetadata()) { return k; } } return null; } } /** * * @param key * @param value * @return true if value has been stored */ public boolean recordSoftReference(CacheKey key, T value) { if (Boolean.getBoolean(NO_CACHING)) return false; synchronized (LOCK) { try { Reference<T> ref = Boolean.getBoolean("uk.ac.diamond.scisoft.analysis.io.weakcaching") ? new WeakReference<T>(value) : new SoftReference<T>(value); cache.put(key, ref); return true; } catch (Throwable ne) { return false; } } } /** * Store data into cache * * @param holder */ public void cacheData(T holder) { internalCacheData(holder, NOT_A_SINGLE_IMAGE); } /** * Store data into cache * * * @param holder * @param imageNumber must be non-negative */ public void cacheData(T holder, int imageNumber) { if (imageNumber < 0) { throw new IllegalArgumentException("Image number must be non-negative"); } internalCacheData(holder, imageNumber); } /** * Store data into cache * * * @param holder * @param imageNumber */ private void internalCacheData(T holder, int imageNumber) { final CacheKey key = new CacheKey(); if (holder instanceof IDataHolder) { IDataHolder h = (IDataHolder)holder; key.setFilePath(h.getFilePath()); key.setMetadata(h.getMetadata() != null); } key.setImageNumber(imageNumber); if (!recordSoftReference(key, holder)) System.err.println("Loader factory failed to cache "+key); } /** * Fetch data from cache * * @param path * @param willLoadMetadata dictates whether metadata is not loaded (if possible) * @return data or null if not in cache */ public T fetchData(String path, boolean willLoadMetadata) { return internalFetchData(path, willLoadMetadata, NOT_A_SINGLE_IMAGE); } /** * Fetch data from cache * * @param path * @param willLoadMetadata dictates whether metadata is not loaded (if possible) * @param imageNumber * @return data or null if not in cache */ public T fetchData(String path, boolean willLoadMetadata, int imageNumber) { if (imageNumber < 0) { throw new IllegalArgumentException("Image number must be non-negative"); } return internalFetchData(path, willLoadMetadata, imageNumber); } /** * Fetch data from cache * * @param path * @param willLoadMetadata dictates whether metadata is not loaded (if possible) * @param imageNumber * @return data or null if not in cache */ private T internalFetchData(String path, boolean willLoadMetadata, int imageNumber) { final CacheKey key = new CacheKey(); key.setFilePath(path); key.setMetadata(willLoadMetadata); key.setImageNumber(imageNumber); final T cachedObject = getSoftReference(key); return cachedObject; } }