/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'Shaven Puppy' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.shavenpuppy.jglib;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Holds Resources. Essentially, a Resource is a handle to a native peer
* represented by a ResourceAllocator which is some native operating system
* entity, like a graphics card or something. This Resources class statically
* keeps hold of all the resources we know that the O/S has so that they won't
* get garbage collected by Java and left in the O/S, or removed from the O/S
* and left in Java. Know This about resources: 1) They must have unique names
* 2) They must all be created and destroyed in the same thread
*
* @author foo
*/
public final class Resources {
public static final boolean DEBUG = false;
static {
if (System.getProperty("java.protocol.handler.pkgs") == null) {
System.getProperties().put("java.protocol.handler.pkgs", "com.shavenpuppy.jglib.resources.protocol");
}
}
/** Serialization mode: set to true to serialize as SerializedResources */
private static boolean runMode;
/** A map of resource names to resources */
private static final Map<String, IResource> RESOURCES = new LinkedHashMap<String, IResource>(256, 0.25f);
/** A map of resource classes to xml tags */
private static final HashMap<Class<? extends IResource>, String> CLASS_TO_TAG_MAP = new HashMap<Class<? extends IResource>, String>();
/** A map of xml tags to resource classes */
private static final HashMap<String, Class<? extends IResource>> TAG_TO_CLASS_MAP = new HashMap<String, Class<? extends IResource>>();
/** A queue of resources that need to be created */
private static final List<IResource> QUEUE = new LinkedList<IResource>();
/** Number of resources created */
private static int numCreated;
/** Resource creation callback */
public interface CreatingCallback {
/**
* Fired when a resource is about to be created
* @param resource
*/
void onCreating(IResource resource);
}
/** Callback listener */
private static CreatingCallback creatingCallback;
/**
* NO constructor for Resources.
*/
private Resources() {
}
/**
* Gets a resource by its string description (as returned by its getName()
* method). The resource is not created.
*
* @param name The name of the resource
* @return the Resource or null if the resource doesn't exist
*/
public static <T extends IResource> T peek(String name) {
return (T) RESOURCES.get(name.toLowerCase());
}
/**
* Gets a resource by its string description (as returned by its getName()
* method). If the resource has not yet been created then it is created and
* this method will block until it has been created.
*
* @param name The name of the resource
* @return the created resource, ready to use, or null if it can't be returned
*/
@SuppressWarnings("unchecked")
public static <T extends IResource> T get(String name) {
if (DEBUG) {
System.err.println("Get resource " + name);
}
IResource ret = RESOURCES.get(name.toLowerCase());
if (ret == null) {
System.err.println("WARNING: Resource '" + name + "' not found");
return null;
}
if (!ret.isCreated()) {
if (creatingCallback != null) {
creatingCallback.onCreating(ret);
}
ret.create();
numCreated++;
}
return (T) ret; // Warning suppressed
}
/**
* @return true if the specified resource exists in the Resources
*/
public static boolean exists(String name) {
return RESOURCES.containsKey(name.toLowerCase());
}
/**
* Puts a resource in the resource map, overwriting any existing resource
* which has the same name. This method will block if necessary.
*
* @param resource The resource to store
*/
public static void put(IResource resource) {
if (resource.getName() == null) {
throw new RuntimeException("Unnamed resource " + resource + " cannot be put in the named set");
}
IResource old = RESOURCES.put(resource.getName().toLowerCase(), resource);
if (old != null) {
old.deregister();
// Note that we don't destroy it - it has to be garbage
// collected...
}
if (DEBUG) {
System.err.println("Put " + resource.getName());
}
resource.register();
}
/**
* Removes a resource by its name. If the resource has been created, it will
* be destroyed and this method will block until it has been destroyed.
*
* @param name The name of the resource
* @return the resource that was removed, if any
*/
public static IResource remove(String name) {
IResource ret;
ret = RESOURCES.remove(name.toLowerCase());
if (ret != null) {
ret.destroy();
ret.deregister();
}
return ret;
}
/**
* Forget a resource.
*
* @param resource
*/
static IResource forget(IResource resource) {
return RESOURCES.remove(resource.getName());
}
/**
* Saves all serializable resources to a stream. The output stream is
* buffered here. Non-serializable resources are not saved.
*
* @param os The output stream to write to.
* @throws IOException if some kind of IO error occurs
*/
public static void save(OutputStream os) throws IOException {
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
oos.writeObject(RESOURCES);
oos.flush();
bos.flush();
} finally {
try {
if (oos != null) {
oos.close();
}
} catch (Exception e) {
}
try {
if (bos != null) {
bos.close();
}
} catch (Exception e) {
}
}
}
/**
* Load serialized resources from a stream. The stream is buffered here.
*
* @param is An input stream to load from.
* @throws IOException If some kind of IO error occurs
*/
public static void load(InputStream is) throws IOException, ClassNotFoundException {
BufferedInputStream bis = new BufferedInputStream(is);
ObjectInputStream ois = new ObjectInputStream(bis);
Map<String, IResource> newAll = (Map<String, IResource>) ois.readObject(); // Warning suppressed
for (Map.Entry<String, IResource> entry : newAll.entrySet()) {
put(entry.getValue());
}
}
/**
* Clear all the resources and de-register them.
*/
public static void clear() {
if (DEBUG) {
System.err.println("------RESOURCES CLEARING------");
}
for (IResource res : RESOURCES.values()) {
if (res != null) {
if (res.isCreated()) {
res.destroy();
}
res.deregister();
}
}
RESOURCES.clear();
System.gc();
if (DEBUG) {
System.err.println("------RESOURCES CLEARED------");
}
}
/**
* Reset all the resources. Any created resources are destroyed.
*/
public static void reset() {
if (DEBUG) {
System.err.println("------RESOURCES RESETTING ------");
}
for (IResource res : RESOURCES.values()) {
if (res != null) {
if (res.isCreated()) {
res.destroy();
}
}
}
System.gc();
if (DEBUG) {
System.err.println("------RESOURCES RESET ------");
}
}
/**
* Returns the total number of resources created.
*
* @return int
*/
public static int getNumCreated() {
return numCreated;
}
/**
* @return Returns the creatingCallback.
*/
public static CreatingCallback getCreatingCallback() {
return creatingCallback;
}
/**
* @param creatingCallback The creatingCallback to set.
*/
public static void setCreatingCallback(CreatingCallback creatingCallback) {
Resources.creatingCallback = creatingCallback;
}
/**
* Get a directory of all named Resources of a particular type
*
* @param clazz The class of Resource
* @return an ArrayList of Resources
*/
public static <T extends IResource> ArrayList<T> list(Class<T> clazz) {
LinkedList<T> ret = new LinkedList<T>();
for (Map.Entry<String, IResource> entry : RESOURCES.entrySet()) {
if (entry.getValue().getName() == null) {
continue;
}
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
ret.add(clazz.cast(entry.getValue()));
}
}
// Convert to a probably more efficient ArrayList...
return new ArrayList<T>(ret);
}
/**
* @return an unmodifiable List of all the Resources
*/
public static List<IResource> list() {
ArrayList<IResource> ret = new ArrayList<IResource>(RESOURCES.values());
return Collections.unmodifiableList(ret);
}
/**
* Controls how resource serialization works. When in development, building
* resource.dat files etc. using the ResourceConverter, leave this as false.
* When resources are serialized to disk they are serialized in their
* entirety.
* <p>
* When you are in a game, you don't actually want to reserialize resources,
* you only want to serialize references to them. Set this flag to true and
* this will be the case.
*
* @param runMode The runMode to set.
*/
public static void setRunMode(boolean runMode) {
Resources.runMode = runMode;
}
/**
* @return Returns the runMode.
*/
public static boolean isRunMode() {
return runMode;
}
public static void registerTag(Class<? extends IResource> clazz, String tag) {
CLASS_TO_TAG_MAP.put(clazz, tag);
TAG_TO_CLASS_MAP.put(tag, clazz);
}
public static String getTag(Class<? extends IResource> clazz) {
return CLASS_TO_TAG_MAP.get(clazz);
}
public static Class<? extends IResource> getMapping(String tag) {
return TAG_TO_CLASS_MAP.get(tag);
}
/**
* Create all uncreated resources
*/
public static void create() {
for (IResource res : RESOURCES.values()) {
res.create();
}
}
static void queue(IResource res) {
QUEUE.add(res);
}
public static void dequeue() {
for (IResource r : QUEUE) {
r.create();
}
QUEUE.clear();
}
}