package com.kartoflane.superluminal2.core;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import com.kartoflane.superluminal2.utils.UIUtils;
/**
* A Cache class that only creates a requested object once, and then
* redistribute the reference to that object to clients.
*
* @author Vhati
* @author kartoFlane
*
*/
public class Cache {
private static final Logger log = LogManager.getLogger(Cache.class);
private static HashMap<String, Image> cachedImageMap = new HashMap<String, Image>();
private static HashMap<RGB, Color> cachedColorMap = new HashMap<RGB, Color>();
private static HashMap<String, ArrayList<Object>> imageCustomerMap = new HashMap<String, ArrayList<Object>>();
private static HashMap<RGB, ArrayList<Object>> colorCustomerMap = new HashMap<RGB, ArrayList<Object>>();
/**
* Request an Image handle for the given path.<br>
* <br>
* All paths must have a protocol declared at their beginning, like so:
*
* <pre>
* <tt>file:C:/example/absolute/path.txt</tt>
* </pre>
*
* If a path is missing its protocol, or if it is mistyped, the image will not be loaded.<br>
* Protocols that this method recognizes:
*
* <pre>
* <tt>file: - for use when the resource is located in the OS' filesystem,
* eg. an absolute or relative path
* cpath: - for use when the resource is located inside the jar
* eg. cpath:/assets/image.png
* db: - for use when the resource is loaded in the database
* eg. db:img/ship/kestral_base.png</tt>
* zip: - for use when the resource is located inside an unloaded zip archive
* eg. zip:/path/to/file.zip/inner/path/image.png
* </pre>
*
* @param customer
* the object that is checking out the image
* @param path
* path to the requested resource, beginning with a protocol
*/
public static Image checkOutImage(Object customer, String path) {
Image image = null;
ArrayList<Object> customers = imageCustomerMap.get(path);
// create a new list of customers if there isn't any
if (customers == null) {
customers = new ArrayList<Object>();
imageCustomerMap.put(path, customers);
}
if (customer == null)
throw new IllegalArgumentException("Customer is null.");
// check whether the image has already been cached
image = cachedImageMap.get(path);
if (image != null && image.isDisposed()) {
cachedImageMap.remove(path);
customers.clear();
image = null;
}
if (path == null) {
throw new IllegalArgumentException("Path is null.");
} else {
try {
if (image == null) {
InputStream is = Manager.getInputStream(path);
image = new Image(UIUtils.getDisplay(), is);
cachedImageMap.put(path, image);
}
customers.add(customer);
} catch (SWTException e) {
log.warn(String.format("%s - resource contains invalid data: %s", path, e.getClass().getSimpleName() + ": " + e.getMessage()));
} catch (IllegalArgumentException e) {
log.warn(String.format("Could not load %s: %s", path, e.getClass().getSimpleName() + ": " + e.getMessage()));
} catch (FileNotFoundException e) {
log.warn("Could not find file: " + path);
}
}
return image;
}
/**
* Signal the Cache that the customer is done using the image.
*/
public static void checkInImage(Object customer, String path) {
ArrayList<Object> customers = imageCustomerMap.get(path);
if (customers != null && customers.size() > 0) {
Iterator<Object> it = customers.iterator();
while (it.hasNext()) {
if (it.next() == customer) {
it.remove();
break;
}
}
}
// no one is using the resource anymore
if (customers == null || customers.size() == 0) {
Image image = cachedImageMap.get(path);
if (image != null && !image.isDisposed())
image.dispose();
cachedImageMap.remove(path);
}
}
/**
* Request a Color handle for the given RGB.
*/
public static Color checkOutColor(Object customer, RGB rgb) {
Color color = null;
ArrayList<Object> customers = colorCustomerMap.get(rgb);
// create a new list of customers if there isn't any
if (customers == null) {
customers = new ArrayList<Object>();
colorCustomerMap.put(rgb, customers);
}
if (customer == null)
throw new IllegalArgumentException("Customer is null.");
// check whether the image has already been cached
color = cachedColorMap.get(rgb);
if (color != null && color.isDisposed()) {
cachedColorMap.remove(rgb);
customers.clear();
color = null;
}
if (rgb == null) {
throw new IllegalArgumentException("RGB is null.");
} else {
if (color == null) {
color = new Color(UIUtils.getDisplay(), rgb);
cachedColorMap.put(rgb, color);
}
customers.add(customer);
}
return color;
}
/**
* Signal the Cache that the customer is done using the color.
*/
public static void checkInColor(Object customer, RGB rgb) {
ArrayList<Object> customers = colorCustomerMap.get(rgb);
if (customers != null && customers.size() > 0) {
Iterator<Object> it = customers.iterator();
while (it.hasNext()) {
if (it.next() == customer) {
it.remove();
break;
}
}
}
// no one is using the resource anymore
if (customers == null || customers.size() == 0) {
Color color = cachedColorMap.get(rgb);
if (color != null && !color.isDisposed())
color.dispose();
cachedColorMap.remove(rgb);
}
}
/** Flush the Cache of any stored Image references, disposing them in the process. */
public static void disposeImages() {
for (Map.Entry<String, Image> entry : cachedImageMap.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isDisposed())
entry.getValue().dispose();
}
cachedImageMap.clear();
imageCustomerMap.clear();
}
/** Flush the Cache of any stored Color references, disposing them in the process. */
public static void disposeColors() {
for (Map.Entry<RGB, Color> entry : cachedColorMap.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isDisposed())
entry.getValue().dispose();
}
cachedColorMap.clear();
colorCustomerMap.clear();
}
/** Flush the Cache of any stored resources, disposing them in the proces. */
public static void dispose() {
disposeImages();
disposeColors();
}
}