Преглед изворни кода

* AssetCache is now an interface and can be extended by user. Moved to com.jme3.asset.cache package.
* Added 3 implementations of AssetCache: SimpleAssetCache, WeakRefAssetCache and WeakRefCloneAssetCache
* Added AssetProcessor interface that handles cloning and post processing of assets after they are loaded
* AssetKey can now configure which cache/processor to use for a particular asset type
* Added AssetManager unregisterLoader method
* AssetManager now supports more than one AssetLoadListener
* Javadoc improvements in AssetManager
* Asset interface now renamed to CloneableSmartAsset (which more accurately describes its behavior and use case)
* DesktopAssetManager now makes proper use of the new AssetProcessor/AssetCache classes when handling asset loading
* Added proper equals/hashCode methods to many AssetKey subclasses, which is required for the new system to work properly
* All AssetKeys were rewritten to work with the new asset system
* loadAsset(AudioKey) now returns an AudioNode and not AudioData, similar to the behavior of loadAsset(TextureKey) returning a Texture and not an Image. Because of that, the key storage in AudioData has been removed.
* Texture, Spatial, and Material are all cloneable smart assets now and will be cleared from the cache when all instances become unreachable
* Improved the existing TestAssetCache test to make sure the new system works

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9309 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Sha..rd пре 13 година
родитељ
комит
bd4214f3bd
25 измењених фајлова са 1263 додато и 552 уклоњено
  1. 7 3
      engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java
  2. 0 131
      engine/src/core/com/jme3/asset/AssetCache.java
  3. 30 39
      engine/src/core/com/jme3/asset/AssetKey.java
  4. 112 28
      engine/src/core/com/jme3/asset/AssetManager.java
  5. 72 0
      engine/src/core/com/jme3/asset/AssetProcessor.java
  6. 52 0
      engine/src/core/com/jme3/asset/CloneableAssetProcessor.java
  7. 19 5
      engine/src/core/com/jme3/asset/CloneableSmartAsset.java
  8. 109 144
      engine/src/core/com/jme3/asset/DesktopAssetManager.java
  9. 154 51
      engine/src/core/com/jme3/asset/ImplHandler.java
  10. 47 11
      engine/src/core/com/jme3/asset/MaterialKey.java
  11. 14 9
      engine/src/core/com/jme3/asset/ModelKey.java
  12. 70 57
      engine/src/core/com/jme3/asset/TextureKey.java
  13. 75 0
      engine/src/core/com/jme3/asset/cache/AssetCache.java
  14. 41 0
      engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java
  15. 88 0
      engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java
  16. 116 0
      engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java
  17. 1 2
      engine/src/core/com/jme3/audio/AudioData.java
  18. 47 3
      engine/src/core/com/jme3/audio/AudioKey.java
  19. 19 0
      engine/src/core/com/jme3/audio/AudioProcessor.java
  20. 2 2
      engine/src/core/com/jme3/material/Material.java
  21. 15 0
      engine/src/core/com/jme3/material/MaterialProcessor.java
  22. 2 2
      engine/src/core/com/jme3/scene/Spatial.java
  23. 2 2
      engine/src/core/com/jme3/texture/Texture.java
  24. 49 0
      engine/src/core/com/jme3/texture/TextureProcessor.java
  25. 120 63
      engine/src/test/jme3test/asset/TestAssetCache.java

+ 7 - 3
engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java

@@ -36,6 +36,7 @@ import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
+import com.jme3.asset.cache.AssetCache;
 import java.io.BufferedReader;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
@@ -78,12 +79,15 @@ public class GLSLLoader implements AssetLoader {
     }
     }
 
 
     private class GlslDependKey extends AssetKey<InputStream> {
     private class GlslDependKey extends AssetKey<InputStream> {
-        public GlslDependKey(String name){
+
+        public GlslDependKey(String name) {
             super(name);
             super(name);
         }
         }
+
         @Override
         @Override
-        public boolean shouldCache(){
-            return false;
+        public Class<? extends AssetCache> getCacheType() {
+            // Disallow caching here
+            return null;
         }
         }
     }
     }
 
 

+ 0 - 131
engine/src/core/com/jme3/asset/AssetCache.java

@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * 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 'jMonkeyEngine' 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.jme3.asset;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.WeakHashMap;
-
-/**
- * An <code>AssetCache</code> allows storage of loaded resources in order
- * to improve their access time if they are requested again in a short period
- * of time. The AssetCache stores weak references to the resources, allowing
- * Java's garbage collector to request deletion of rarely used resources
- * when heap memory is low.
- */
-public class AssetCache {
-
-    public static final class SmartAssetInfo {
-        public WeakReference<AssetKey> smartKey;
-        public Asset asset;
-    }
-
-    private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
-            = new WeakHashMap<AssetKey, SmartAssetInfo>();
-    private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();
-
-    /**
-     * Adds a resource to the cache.
-     * <br/><br/>
-     * <font color="red">Thread-safe.</font>
-     * @see #getFromCache(java.lang.String)
-     */
-    public void addToCache(AssetKey key, Object obj){
-        synchronized (regularCache){
-            if (obj instanceof Asset && key.useSmartCache()){
-                // put in smart cache
-                Asset asset = (Asset) obj;
-                asset.setKey(null); // no circular references
-                SmartAssetInfo smartInfo = new SmartAssetInfo();
-                smartInfo.asset = asset;
-                // use the original key as smart key
-                smartInfo.smartKey = new WeakReference<AssetKey>(key); 
-                smartCache.put(key, smartInfo);
-            }else{
-                // put in regular cache
-                regularCache.put(key, obj);
-            }
-        }
-    }
-
-    /**
-     * Delete an asset from the cache, returns true if it was deleted successfuly.
-     * <br/><br/>
-     * <font color="red">Thread-safe.</font>
-     */
-    public boolean deleteFromCache(AssetKey key){
-        synchronized (regularCache){
-            if (key.useSmartCache()){
-                return smartCache.remove(key) != null;
-            }else{
-                return regularCache.remove(key) != null;
-            }
-        }
-    }
-
-    /**
-     * Gets an object from the cache given an asset key.
-     * <br/><br/>
-     * <font color="red">Thread-safe.</font>
-     * @param key
-     * @return the object matching the {@link AssetKey}
-     */
-    public Object getFromCache(AssetKey key){
-        synchronized (regularCache){
-            if (key.useSmartCache()) {
-                return smartCache.get(key).asset;
-            } else {
-                return regularCache.get(key);
-            }
-        }
-    }
-
-    /**
-     * Retrieves smart asset info from the cache.
-     * @param key
-     * @return
-     */
-    public SmartAssetInfo getFromSmartCache(AssetKey key){
-        return smartCache.get(key);
-    }
-
-    /**
-     * Deletes all the assets in the regular cache.
-     */
-    public void deleteAllAssets(){
-        synchronized (regularCache){
-            regularCache.clear();
-            smartCache.clear();
-        }
-    }
-}

+ 30 - 39
engine/src/core/com/jme3/asset/AssetKey.java

@@ -32,6 +32,8 @@
 
 
 package com.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.SimpleAssetCache;
 import com.jme3.export.*;
 import com.jme3.export.*;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.LinkedList;
 import java.util.LinkedList;
@@ -55,7 +57,7 @@ public class AssetKey<T> implements Savable {
     public AssetKey(){
     public AssetKey(){
     }
     }
 
 
-    protected static String getExtension(String name){
+    protected static String getExtension(String name) {
         int idx = name.lastIndexOf('.');
         int idx = name.lastIndexOf('.');
         //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
         //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
         if (name.toLowerCase().endsWith(".xml")) {
         if (name.toLowerCase().endsWith(".xml")) {
@@ -64,20 +66,25 @@ public class AssetKey<T> implements Savable {
                 idx = name.lastIndexOf('.');
                 idx = name.lastIndexOf('.');
             }
             }
         }
         }
-        if (idx <= 0 || idx == name.length() - 1)
+        if (idx <= 0 || idx == name.length() - 1) {
             return "";
             return "";
-        else
-            return name.substring(idx+1).toLowerCase();
+        } else {
+            return name.substring(idx + 1).toLowerCase();
+        }
     }
     }
 
 
-    protected static String getFolder(String name){
+    protected static String getFolder(String name) {
         int idx = name.lastIndexOf('/');
         int idx = name.lastIndexOf('/');
-        if (idx <= 0 || idx == name.length() - 1)
+        if (idx <= 0 || idx == name.length() - 1) {
             return "";
             return "";
-        else
-            return name.substring(0, idx+1);
+        } else {
+            return name.substring(0, idx + 1);
+        }
     }
     }
 
 
+    /**
+     * @return The asset path
+     */
     public String getName() {
     public String getName() {
         return name;
         return name;
     }
     }
@@ -90,6 +97,11 @@ public class AssetKey<T> implements Savable {
         return extension;
         return extension;
     }
     }
 
 
+    /**
+     * @return The folder in which the asset is located in.
+     * E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o" 
+     * then "Models/MyModel/" is returned.
+     */
     public String getFolder(){
     public String getFolder(){
         if (folder == null)
         if (folder == null)
             folder = getFolder(name);
             folder = getFolder(name);
@@ -98,41 +110,20 @@ public class AssetKey<T> implements Savable {
     }
     }
 
 
     /**
     /**
-     * Do any post-processing on the resource after it has been loaded.
-     * @param asset
-     */
-    public Object postProcess(Object asset){
-        return asset;
-    }
-
-    /**
-     * Create a new instance of the asset, based on a prototype that is stored
-     * in the cache. Implementations are allowed to return the given parameter
-     * as-is if it is considered that cloning is not necessary for that particular
-     * asset type.
-     * 
-     * @param asset The asset to be cloned.
-     * @return The asset, possibly cloned.
-     */
-    public Object createClonedInstance(Object asset){
-        return asset;
-    }
-
-    /**
-     * @return True if the asset for this key should be cached. Subclasses
-     * should override this method if they want to override caching behavior.
+     * @return The preferred cache class for this asset type. Specify "null"
+     * if caching is to be disabled. By default the 
+     * {@link SimpleAssetCache} is returned.
      */
      */
-    public boolean shouldCache(){
-        return true;
+    public Class<? extends AssetCache> getCacheType(){
+        return SimpleAssetCache.class;
     }
     }
-
+    
     /**
     /**
-     * @return Should return true, if the asset objects implement the "Asset"
-     * interface and want to be removed from the cache when no longer
-     * referenced in user-code.
+     * @return The preferred processor type for this asset type. Specify "null"
+     * if no processing is required.
      */
      */
-    public boolean useSmartCache(){
-        return false;
+    public Class<? extends AssetProcessor> getProcessorType(){
+        return null;
     }
     }
     
     
     /**
     /**

+ 112 - 28
engine/src/core/com/jme3/asset/AssetManager.java

@@ -32,65 +32,123 @@
 
 
 package com.jme3.asset;
 package com.jme3.asset;
 
 
-import com.jme3.audio.AudioData;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.asset.plugins.FileLocator;
 import com.jme3.audio.AudioKey;
 import com.jme3.audio.AudioKey;
+import com.jme3.audio.AudioNode;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapFont;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.OBJLoader;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderKey;
 import com.jme3.shader.ShaderKey;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
+import com.jme3.texture.plugins.TGALoader;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
  * <code>AssetManager</code> provides an interface for managing the data assets
  * <code>AssetManager</code> provides an interface for managing the data assets
  * of a jME3 application.
  * of a jME3 application.
+ * <p>
+ * The asset manager provides a means to register {@link AssetLocator}s,
+ * which are used to find asset data on disk, network, or other file system.
+ * The asset locators are invoked in order of addition to find the asset data.
+ * Use the {@link #registerLocator(java.lang.String, java.lang.Class) } method
+ * to add new {@link AssetLocator}s. 
+ * Some examples of locators:
+ * <ul>
+ * <li>{@link FileLocator} - Used to find assets on the local file system.</li>
+ * <li>{@link ClasspathLocator} - Used to find assets in the Java classpath</li>
+ * </ul>
+ * <p>
+ * The asset data is represented by the {@link AssetInfo} class, this
+ * data is passed into the registered {@link AssetLoader}s in order to 
+ * convert the data into a usable object. Use the
+ * {@link #registerLoader(java.lang.Class, java.lang.String[]) } method
+ * to add loaders.
+ * Some examples of loaders:
+ * <ul>
+ * <li>{@link OBJLoader} - Used to load Wavefront .OBJ model files</li>
+ * <li>{@link TGALoader} - Used to load Targa image files</li>
+ * </ul>
+ * <p>
+ * Once the asset has been loaded, 
  */
  */
 public interface AssetManager {
 public interface AssetManager {
 
 
     /**
     /**
-     * Adds a ClassLoader that is used to load *Classes* that are needed for Assets like j3o models.
-     * This does *not* allow loading assets from that classpath, use registerLocator for that.
-     * @param loader A ClassLoader that Classes in asset files can be loaded from
+     * Adds a {@link ClassLoader} that is used to load {@link Class classes}
+     * that are needed for finding and loading Assets. 
+     * This does <strong>not</strong> allow loading assets from that classpath, 
+     * use registerLocator for that.
+     * 
+     * @param loader A ClassLoader that Classes in asset files can be loaded from.
      */
      */
     public void addClassLoader(ClassLoader loader);
     public void addClassLoader(ClassLoader loader);
 
 
     /**
     /**
-     * Remove a ClassLoader from the list of registered ClassLoaders
+     * Remove a {@link ClassLoader} from the list of registered ClassLoaders
      */
      */
     public void removeClassLoader(ClassLoader loader);
     public void removeClassLoader(ClassLoader loader);
 
 
     /**
     /**
-     * Retrieve the list of registered ClassLoaders that are used for loading Classes from
-     * asset files.
+     * Retrieve the list of registered ClassLoaders that are used for loading 
+     * {@link Class classes} from asset files.
      */
      */
     public List<ClassLoader> getClassLoaders();
     public List<ClassLoader> getClassLoaders();
     
     
     /**
     /**
      * Registers a loader for the given extensions.
      * Registers a loader for the given extensions.
+     * 
      * @param loaderClassName
      * @param loaderClassName
      * @param extensions
      * @param extensions
+     * 
+     * @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) }
+     * together with {@link Class#forName(java.lang.String) } to find a class
+     * and then register it.
+     * 
+     * @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) }
+     * with {@link Class#forName(java.lang.String) } instead.
      */
      */
+    @Deprecated
     public void registerLoader(String loaderClassName, String ... extensions);
     public void registerLoader(String loaderClassName, String ... extensions);
 
 
     /**
     /**
-     * Registers an {@link AssetLocator} by using a class name, instead of 
-     * a class instance. See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
+     * Registers an {@link AssetLocator} by using a class name. 
+     * See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
      * method for more information.
      * method for more information.
      *
      *
-     * @param rootPath The root path from which to locate assets, implementation
-     * dependent.
+     * @param rootPath The root path from which to locate assets, this 
+     * depends on the implementation of the asset locator. 
+     * A URL based locator will expect a url folder such as "http://www.example.com/"
+     * while a File based locator will expect a file path (OS dependent).
      * @param locatorClassName The full class name of the {@link AssetLocator}
      * @param locatorClassName The full class name of the {@link AssetLocator}
      * implementation.
      * implementation.
+     * 
+     * @deprecated Please use {@link #registerLocator(java.lang.String, java.lang.Class)  }
+     * together with {@link Class#forName(java.lang.String) } to find a class
+     * and then register it.
      */
      */
+    @Deprecated
     public void registerLocator(String rootPath, String locatorClassName);
     public void registerLocator(String rootPath, String locatorClassName);
 
 
     /**
     /**
-     *
+     * Register an {@link AssetLoader} by using a class object.
+     * 
      * @param loaderClass
      * @param loaderClass
      * @param extensions
      * @param extensions
      */
      */
     public void registerLoader(Class<? extends AssetLoader> loaderClass, String ... extensions);
     public void registerLoader(Class<? extends AssetLoader> loaderClass, String ... extensions);
+    
+    /**
+     * Unregister a {@link AssetLoader} from loading its assigned extensions.
+     * This undoes the effect of calling 
+     * {@link #registerLoader(java.lang.Class, java.lang.String[]) }.
+     * 
+     * @param loaderClass The loader class to unregister.
+     * @see #registerLoader(java.lang.Class, java.lang.String[]) 
+     */
+    public void unregisterLoader(Class<? extends AssetLoader> loaderClass);
 
 
     /**
     /**
      * Registers the given locator class for locating assets with this
      * Registers the given locator class for locating assets with this
@@ -119,16 +177,42 @@ public interface AssetManager {
      * @param rootPath Should be the same as the root path specified in {@link
      * @param rootPath Should be the same as the root path specified in {@link
      * #registerLocator(java.lang.String, java.lang.Class) }.
      * #registerLocator(java.lang.String, java.lang.Class) }.
      * @param locatorClass The locator class to unregister
      * @param locatorClass The locator class to unregister
+     * 
+     * @see #registerLocator(java.lang.String, java.lang.Class) 
      */
      */
     public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
     public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
     
     
+    /**
+     * Add an {@link AssetEventListener} to receive events from this
+     * <code>AssetManager</code>. 
+     * @param listener The asset event listener to add
+     */
+    public void addAssetEventListener(AssetEventListener listener);
+    
+    /**
+     * Remove an {@link AssetEventListener} from receiving events from this
+     * <code>AssetManager</code>
+     * @param listener The asset event listener to remove
+     */
+    public void removeAssetEventListener(AssetEventListener listener);
+    
+    /**
+     * Removes all asset event listeners.
+     * 
+     * @see #addAssetEventListener(com.jme3.asset.AssetEventListener) 
+     */
+    public void clearAssetEventListeners();
+    
     /**
     /**
      * Set an {@link AssetEventListener} to receive events from this
      * Set an {@link AssetEventListener} to receive events from this
-     * <code>AssetManager</code>. There can only be one {@link  AssetEventListener}
-     * associated with an <code>AssetManager</code>
+     * <code>AssetManager</code>. Any currently added listeners are
+     * cleared and then the given listener is added.
      * 
      * 
-     * @param listener
+     * @param listener The listener to set
+     * @deprecated Please use {@link #addAssetEventListener(com.jme3.asset.AssetEventListener) }
+     * to listen for asset events.
      */
      */
+    @Deprecated
     public void setAssetEventListener(AssetEventListener listener);
     public void setAssetEventListener(AssetEventListener listener);
 
 
     /**
     /**
@@ -162,7 +246,7 @@ public interface AssetManager {
     public <T> T loadAsset(AssetKey<T> key);
     public <T> T loadAsset(AssetKey<T> key);
 
 
     /**
     /**
-     * Load a named asset by name, calling this method
+     * Load an asset by name, calling this method
      * is the same as calling
      * is the same as calling
      * <code>
      * <code>
      * loadAsset(new AssetKey(name)).
      * loadAsset(new AssetKey(name)).
@@ -199,27 +283,27 @@ public interface AssetManager {
 
 
     /**
     /**
      * Load audio file, supported types are WAV or OGG.
      * Load audio file, supported types are WAV or OGG.
-     * @param key
+     * @param key Asset key of the audio file to load
      * @return The audio data loaded
      * @return The audio data loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      */
      */
-    public AudioData loadAudio(AudioKey key);
+    public AudioNode loadAudio(AudioKey key);
 
 
     /**
     /**
      * Load audio file, supported types are WAV or OGG.
      * Load audio file, supported types are WAV or OGG.
      * The file is loaded without stream-mode.
      * The file is loaded without stream-mode.
-     * @param name
+     * @param name Asset name of the audio file to load
      * @return The audio data loaded
      * @return The audio data loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      */
      */
-    public AudioData loadAudio(String name);
+    public AudioNode loadAudio(String name);
 
 
     /**
     /**
-     * Loads a named model. Models can be jME3 object files (J3O) or
-     * OgreXML/OBJ files.
-     * @param key
+     * Loads a 3D model with a ModelKey. 
+     * Models can be jME3 object files (J3O) or OgreXML/OBJ files.
+     * @param key Asset key of the model to load
      * @return The model that was loaded
      * @return The model that was loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@@ -227,9 +311,9 @@ public interface AssetManager {
     public Spatial loadModel(ModelKey key);
     public Spatial loadModel(ModelKey key);
 
 
     /**
     /**
-     * Loads a named model. Models can be jME3 object files (J3O) or
+     * Loads a 3D model. Models can be jME3 object files (J3O) or
      * OgreXML/OBJ files.
      * OgreXML/OBJ files.
-     * @param name
+     * @param name Asset name of the model to load
      * @return The model that was loaded
      * @return The model that was loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@@ -237,8 +321,8 @@ public interface AssetManager {
     public Spatial loadModel(String name);
     public Spatial loadModel(String name);
 
 
     /**
     /**
-     * Load a material (J3M) file.
-     * @param name
+     * Load a material instance (J3M) file.
+     * @param name Asset name of the material to load
      * @return The material that was loaded
      * @return The material that was loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@@ -256,7 +340,7 @@ public interface AssetManager {
      * Load a font file. Font files are in AngelCode text format,
      * Load a font file. Font files are in AngelCode text format,
      * and are with the extension "fnt".
      * and are with the extension "fnt".
      *
      *
-     * @param name
+     * @param name Asset name of the font to load
      * @return The font loaded
      * @return The font loaded
      *
      *
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) 
      * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) 

+ 72 - 0
engine/src/core/com/jme3/asset/AssetProcessor.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * 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 'jMonkeyEngine' 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.jme3.asset;
+
+import com.jme3.material.Material;
+import com.jme3.shader.Shader;
+
+/**
+ * <code>AssetProcessor</code> is used to apply processing to assets
+ * after they have been loaded. They are assigned to a particular
+ * asset type (which is represented by a {@link Class} and any assets
+ * loaded that are of that class will be processed by the assigned
+ * processor.
+ * 
+ * @author Kirill Vainer
+ */
+public interface AssetProcessor {
+    /**
+     * Applies post processing to an asset.
+     * The method may return an object that is not the same
+     * instance as the parameter object, and it could be from a different class.
+     * 
+     * @param obj The asset that was loaded from an {@link AssetLoader}.
+     * @return Either the same object with processing applied, or an instance
+     * of a new object.
+     */
+    public Object postProcess(AssetKey key, Object obj);
+    
+    /**
+     * Creates a clone of the given asset.
+     * If no clone is desired, then the same instance can be returned,
+     * otherwise, a clone should be created.
+     * For example, a clone of a {@link Material} should have its own set
+     * of unique parameters that can be changed just for that instance,
+     * but it may share certain other data if it sees fit (like the {@link Shader}).
+     * 
+     * @param obj The asset to clone
+     * @return The cloned asset, or the same as the given argument if no
+     * clone is needed.
+     */
+    public Object createClone(Object obj);
+}

+ 52 - 0
engine/src/core/com/jme3/asset/CloneableAssetProcessor.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * 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 'jMonkeyEngine' 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.jme3.asset;
+
+/**
+ * <code>CloneableAssetProcessor</code> simply calls {@link Object#clone() }
+ * on assets to clone them. No processing is applied.
+ * 
+ * @author Kirill Vainer
+ */
+public class CloneableAssetProcessor implements AssetProcessor {
+
+    public Object postProcess(AssetKey key, Object obj) {
+        return obj;
+    }
+
+    public Object createClone(Object obj) {
+        CloneableSmartAsset asset = (CloneableSmartAsset) obj;
+        return asset.clone();
+    }
+    
+}

+ 19 - 5
engine/src/core/com/jme3/asset/Asset.java → engine/src/core/com/jme3/asset/CloneableSmartAsset.java

@@ -32,14 +32,17 @@
 
 
 package com.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
+
 /**
 /**
- * Implementing the asset interface allows use of smart asset management.
+ * Implementing the <code>CloneableSmartAsset</code> interface allows use 
+ * of cloneable smart asset management.
  * <p>
  * <p>
  * Smart asset management requires cooperation from the {@link AssetKey}. 
  * Smart asset management requires cooperation from the {@link AssetKey}. 
- * In particular, the AssetKey should return true in its 
- * {@link AssetKey#useSmartCache() } method. Also smart assets MUST
+ * In particular, the AssetKey should return {@link WeakRefCloneAssetCache} in its 
+ * {@link AssetKey#getCacheType()} method. Also smart assets MUST
  * create a clone of the asset and cannot return the same reference,
  * create a clone of the asset and cannot return the same reference,
- * e.g. {@link AssetKey#createClonedInstance(java.lang.Object) createCloneInstance(someAsset)} <code>!= someAsset</code>.
+ * e.g. {@link AssetProcessor#createClone(java.lang.Object) createClone(someAsset)} <code>!= someAsset</code>.
  * <p>
  * <p>
  * If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method
  * If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method
  * is called twice with the same asset key (equals() wise, not necessarily reference wise)
  * is called twice with the same asset key (equals() wise, not necessarily reference wise)
@@ -49,7 +52,18 @@ package com.jme3.asset;
  * are garbage collected, the shared asset key becomes unreachable and at that 
  * are garbage collected, the shared asset key becomes unreachable and at that 
  * point it is removed from the smart asset cache. 
  * point it is removed from the smart asset cache. 
  */
  */
-public interface Asset {
+public interface CloneableSmartAsset extends Cloneable {
+    
+    /**
+     * Creates a clone of the asset. 
+     * 
+     * Please see {@link Object#clone() } for more info on how this method
+     * should be implemented. 
+     * 
+     * @return A clone of this asset. 
+     * The cloned asset cannot reference equal this asset.
+     */
+    public Object clone();
     
     
     /**
     /**
      * Set by the {@link AssetManager} to track this asset. 
      * Set by the {@link AssetManager} to track this asset. 

+ 109 - 144
engine/src/core/com/jme3/asset/DesktopAssetManager.java

@@ -32,9 +32,10 @@
 
 
 package com.jme3.asset;
 package com.jme3.asset;
 
 
-import com.jme3.asset.AssetCache.SmartAssetInfo;
-import com.jme3.audio.AudioData;
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.SimpleAssetCache;
 import com.jme3.audio.AudioKey;
 import com.jme3.audio.AudioKey;
+import com.jme3.audio.AudioNode;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapFont;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
@@ -48,6 +49,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
@@ -60,15 +62,14 @@ import java.util.logging.Logger;
 public class DesktopAssetManager implements AssetManager {
 public class DesktopAssetManager implements AssetManager {
 
 
     private static final Logger logger = Logger.getLogger(AssetManager.class.getName());
     private static final Logger logger = Logger.getLogger(AssetManager.class.getName());
-
-    private final AssetCache cache = new AssetCache();
+    
     private final ImplHandler handler = new ImplHandler(this);
     private final ImplHandler handler = new ImplHandler(this);
 
 
-    private AssetEventListener eventListener = null;
-    private List<ClassLoader> classLoaders;
-
-//    private final ThreadingManager threadingMan = new ThreadingManager(this);
-//    private final Set<AssetKey> alreadyLoadingSet = new HashSet<AssetKey>();
+    private CopyOnWriteArrayList<AssetEventListener> eventListeners = 
+            new CopyOnWriteArrayList<AssetEventListener>();
+    
+    private List<ClassLoader> classLoaders =
+            Collections.synchronizedList(new ArrayList<ClassLoader>());
 
 
     public DesktopAssetManager(){
     public DesktopAssetManager(){
         this(null);
         this(null);
@@ -81,44 +82,55 @@ public class DesktopAssetManager implements AssetManager {
 
 
     public DesktopAssetManager(URL configFile){
     public DesktopAssetManager(URL configFile){
         if (configFile != null){
         if (configFile != null){
-            InputStream stream = null;
-            try{
-                AssetConfig cfg = new AssetConfig(this);
-                stream = configFile.openStream();
-                cfg.loadText(stream);
-            }catch (IOException ex){
-                logger.log(Level.SEVERE, "Failed to load asset config", ex);
-            }finally{
-                if (stream != null)
-                    try{
-                        stream.close();
-                    }catch (IOException ex){
-                    }
-            }
+            loadConfigFile(configFile);
         }
         }
         logger.info("DesktopAssetManager created.");
         logger.info("DesktopAssetManager created.");
     }
     }
 
 
-    public void addClassLoader(ClassLoader loader){
-        if(classLoaders == null)
-            classLoaders = Collections.synchronizedList(new ArrayList<ClassLoader>());
-        synchronized(classLoaders) {
-            classLoaders.add(loader);
+    private void loadConfigFile(URL configFile){
+        InputStream stream = null;
+        try{
+            AssetConfig cfg = new AssetConfig(this);
+            stream = configFile.openStream();
+            cfg.loadText(stream);
+        }catch (IOException ex){
+            logger.log(Level.SEVERE, "Failed to load asset config", ex);
+        }finally{
+            if (stream != null)
+                try{
+                    stream.close();
+                }catch (IOException ex){
+                }
         }
         }
     }
     }
     
     
-    public void removeClassLoader(ClassLoader loader){
-        if(classLoaders != null) synchronized(classLoaders) {
-                classLoaders.remove(loader);
-            }
+    public void addClassLoader(ClassLoader loader) {
+        classLoaders.add(loader);
+    }
+    
+    public void removeClassLoader(ClassLoader loader) {
+        classLoaders.remove(loader);
     }
     }
 
 
     public List<ClassLoader> getClassLoaders(){
     public List<ClassLoader> getClassLoaders(){
-        return classLoaders;
+        return Collections.unmodifiableList(classLoaders);
+    }
+    
+    public void addAssetEventListener(AssetEventListener listener) {
+        eventListeners.add(listener);
+    }
+
+    public void removeAssetEventListener(AssetEventListener listener) {
+        eventListeners.remove(listener);
+    }
+
+    public void clearAssetEventListeners() {
+        eventListeners.clear();
     }
     }
     
     
     public void setAssetEventListener(AssetEventListener listener){
     public void setAssetEventListener(AssetEventListener listener){
-        eventListener = listener;
+        eventListeners.clear();
+        eventListeners.add(listener);
     }
     }
 
 
     public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){
     public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){
@@ -142,6 +154,14 @@ public class DesktopAssetManager implements AssetManager {
             registerLoader(clazz, extensions);
             registerLoader(clazz, extensions);
         }
         }
     }
     }
+    
+    public void unregisterLoader(Class<? extends AssetLoader> loaderClass) {
+        handler.removeLoader(loaderClass);
+        if (logger.isLoggable(Level.FINER)){
+            logger.log(Level.FINER, "Unregistered loader: {0}",
+                    loaderClass.getSimpleName());
+        }
+    }
 
 
     public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){
     public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){
         handler.addLocator(locatorClass, rootPath);
         handler.addLocator(locatorClass, rootPath);
@@ -172,39 +192,15 @@ public class DesktopAssetManager implements AssetManager {
                     clazz.getSimpleName());
                     clazz.getSimpleName());
         }
         }
     }
     }
-
+    
     public void clearCache(){
     public void clearCache(){
-        cache.deleteAllAssets();
-    }
-
-    /**
-     * Delete an asset from the cache, returns true if it was deleted
-     * successfully.
-     * <br/><br/>
-     * <font color="red">Thread-safe.</font>
-     */
-    public boolean deleteFromCache(AssetKey key){
-        return cache.deleteFromCache(key);
-    }
-
-    /**
-     * Adds a resource to the cache.
-     * <br/><br/>
-     * <font color="red">Thread-safe.</font>
-     */
-    public void addToCache(AssetKey key, Object asset){
-        cache.addToCache(key, asset);
+        handler.clearCache();
+        if (logger.isLoggable(Level.FINER)){
+            logger.log(Level.FINER, "All asset caches cleared.");
+        }
     }
     }
 
 
     public AssetInfo locateAsset(AssetKey<?> key){
     public AssetInfo locateAsset(AssetKey<?> key){
-        if (handler.getLocatorCount() == 0){
-            logger.warning("There are no locators currently"+
-                           " registered. Use AssetManager."+
-                           "registerLocator() to register a"+
-                           " locator.");
-            return null;
-        }
-
         AssetInfo info = handler.tryLocate(key);
         AssetInfo info = handler.tryLocate(key);
         if (info == null){
         if (info == null){
             logger.log(Level.WARNING, "Cannot locate resource: {0}", key);
             logger.log(Level.WARNING, "Cannot locate resource: {0}", key);
@@ -224,91 +220,83 @@ public class DesktopAssetManager implements AssetManager {
         if (key == null)
         if (key == null)
             throw new IllegalArgumentException("key cannot be null");
             throw new IllegalArgumentException("key cannot be null");
         
         
-        if (eventListener != null)
-            eventListener.assetRequested(key);
-
-        AssetKey smartKey = null;
-        Object o = null;
-        if (key.shouldCache()){
-            if (key.useSmartCache()){
-                SmartAssetInfo smartInfo = cache.getFromSmartCache(key);
-                if (smartInfo != null){
-                    smartKey = smartInfo.smartKey.get();
-                    if (smartKey != null){
-                        o = smartInfo.asset;
-                    }
-                }
-            }else{
-                o = cache.getFromCache(key);
-            }
+        for (AssetEventListener listener : eventListeners){
+            listener.assetRequested(key);
         }
         }
-        if (o == null){
+        
+        AssetCache cache = handler.getCache(key.getCacheType());
+        AssetProcessor proc = handler.getProcessor(key.getProcessorType());
+        
+        Object obj = cache != null ? cache.getFromCache(key) : null;
+        if (obj == null){
+            // Asset not in cache, load it from file system.
             AssetLoader loader = handler.aquireLoader(key);
             AssetLoader loader = handler.aquireLoader(key);
-            if (loader == null){
-                throw new IllegalStateException("No loader registered for type \"" +
-                                                key.getExtension() + "\"");
-            }
-
-            if (handler.getLocatorCount() == 0){
-                throw new IllegalStateException("There are no locators currently"+
-                                                " registered. Use AssetManager."+
-                                                "registerLocator() to register a"+
-                                                " locator.");
-            }
-
             AssetInfo info = handler.tryLocate(key);
             AssetInfo info = handler.tryLocate(key);
             if (info == null){
             if (info == null){
-                if (handler.getParentKey() != null && eventListener != null){
+                if (handler.getParentKey() != null){
                     // Inform event listener that an asset has failed to load.
                     // Inform event listener that an asset has failed to load.
                     // If the parent AssetLoader chooses not to propagate
                     // If the parent AssetLoader chooses not to propagate
                     // the exception, this is the only means of finding
                     // the exception, this is the only means of finding
                     // that something went wrong.
                     // that something went wrong.
-                    eventListener.assetDependencyNotFound(handler.getParentKey(), key);
+                    for (AssetEventListener listener : eventListeners){
+                        listener.assetDependencyNotFound(handler.getParentKey(), key);
+                    }
                 }
                 }
                 throw new AssetNotFoundException(key.toString());
                 throw new AssetNotFoundException(key.toString());
             }
             }
 
 
             try {
             try {
                 handler.establishParentKey(key);
                 handler.establishParentKey(key);
-                o = loader.load(info);
+                obj = loader.load(info);
             } catch (IOException ex) {
             } catch (IOException ex) {
                 throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);
                 throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);
             } finally {
             } finally {
                 handler.releaseParentKey(key);
                 handler.releaseParentKey(key);
             }
             }
-            if (o == null){
-                throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using" + loader.getClass().getSimpleName());
+            if (obj == null){
+                throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using " + loader.getClass().getSimpleName());
             }else{
             }else{
                 if (logger.isLoggable(Level.FINER)){
                 if (logger.isLoggable(Level.FINER)){
                     logger.log(Level.FINER, "Loaded {0} with {1}",
                     logger.log(Level.FINER, "Loaded {0} with {1}",
                             new Object[]{key, loader.getClass().getSimpleName()});
                             new Object[]{key, loader.getClass().getSimpleName()});
                 }
                 }
                 
                 
-                // do processing on asset before caching
-                o = key.postProcess(o);
-
-                if (key.shouldCache())
-                    cache.addToCache(key, o);
-
-                if (eventListener != null)
-                    eventListener.assetLoaded(key);
+                if (proc != null){
+                    // do processing on asset before caching
+                    obj = proc.postProcess(key, obj);
+                }
+                
+                if (cache != null){
+                    // At this point, obj should be of type T
+                    cache.addToCache(key, (T) obj);
+                }
+                
+                for (AssetEventListener listener : eventListeners){
+                    listener.assetLoaded(key);
+                }
             }
             }
         }
         }
 
 
-        // object o is the asset
+        // object obj is the original asset
         // create an instance for user
         // create an instance for user
-        T clone = (T) key.createClonedInstance(o);
-
-        if (key.useSmartCache()){
-            if (smartKey != null){
-                // smart asset was already cached, use original key
-                ((Asset)clone).setKey(smartKey);
+        T clone = (T) obj;
+        if (clone instanceof CloneableSmartAsset){
+            if (proc == null){
+                throw new IllegalStateException("Asset implements "
+                        + "CloneableSmartAsset but doesn't "
+                        + "have processor to handle cloning");
             }else{
             }else{
-                // smart asset was cached on this call, use our key
-                ((Asset)clone).setKey(key);
+                clone = (T) proc.createClone(obj);
+                if (cache != null && clone != obj){
+                    cache.registerAssetClone(key, clone);
+                } else{
+                    throw new IllegalStateException("Asset implements "
+                        + "CloneableSmartAsset but doesn't have cache or "
+                        + "was not cloned");
+                }
             }
             }
         }
         }
-        
+       
         return clone;
         return clone;
     }
     }
 
 
@@ -329,38 +317,15 @@ public class DesktopAssetManager implements AssetManager {
         return (Material) loadAsset(new MaterialKey(name));
         return (Material) loadAsset(new MaterialKey(name));
     }
     }
 
 
-    /**
-     * Loads a texture.
-     *
-     * @param name
-     * @param generateMipmaps Enable if applying texture to 3D objects, disable
-     * for GUI/HUD elements.
-     * @return the loaded texture
-     */
-    public Texture loadTexture(String name, boolean generateMipmaps){
-        TextureKey key = new TextureKey(name, true);
-        key.setGenerateMips(generateMipmaps);
-        key.setAsCube(false);
-        return loadTexture(key);
-    }
-
-    public Texture loadTexture(String name, boolean generateMipmaps, boolean flipY, boolean asCube, int aniso){
-        TextureKey key = new TextureKey(name, flipY);
-        key.setGenerateMips(generateMipmaps);
-        key.setAsCube(asCube);
-        key.setAnisotropy(aniso);
-        return loadTexture(key);
-    }
-
     public Texture loadTexture(String name){
     public Texture loadTexture(String name){
-        return loadTexture(name, true);
+        return loadTexture(new TextureKey(name, false));
     }
     }
 
 
-    public AudioData loadAudio(AudioKey key){
-        return (AudioData) loadAsset(key);
+    public AudioNode loadAudio(AudioKey key){
+        return (AudioNode) loadAsset(key);
     }
     }
 
 
-    public AudioData loadAudio(String name){
+    public AudioNode loadAudio(String name){
         return loadAudio(new AudioKey(name, false));
         return loadAudio(new AudioKey(name, false));
     }
     }
 
 
@@ -387,6 +352,7 @@ public class DesktopAssetManager implements AssetManager {
     public Shader loadShader(ShaderKey key){
     public Shader loadShader(ShaderKey key){
         // cache abuse in method
         // cache abuse in method
         // that doesn't use loaders/locators
         // that doesn't use loaders/locators
+        AssetCache cache = handler.getCache(SimpleAssetCache.class);
         Shader s = (Shader) cache.getFromCache(key);
         Shader s = (Shader) cache.getFromCache(key);
         if (s == null){
         if (s == null){
             String vertName = key.getVertName();
             String vertName = key.getVertName();
@@ -417,5 +383,4 @@ public class DesktopAssetManager implements AssetManager {
     public Spatial loadModel(String name){
     public Spatial loadModel(String name){
         return loadModel(new ModelKey(name));
         return loadModel(new ModelKey(name));
     }
     }
-    
 }
 }

+ 154 - 51
engine/src/core/com/jme3/asset/ImplHandler.java

@@ -32,9 +32,12 @@
 
 
 package com.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.AssetCache;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
@@ -49,40 +52,62 @@ public class ImplHandler {
 
 
     private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
     private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
 
 
-    private final AssetManager owner;
+    private final AssetManager assetManager;
     
     
     private final ThreadLocal<AssetKey> parentAssetKey 
     private final ThreadLocal<AssetKey> parentAssetKey 
             = new ThreadLocal<AssetKey>();
             = new ThreadLocal<AssetKey>();
     
     
-    private final ArrayList<ImplThreadLocal> genericLocators =
-                new ArrayList<ImplThreadLocal>();
+    private final CopyOnWriteArrayList<ImplThreadLocal<AssetLocator>> locatorsList =
+                new CopyOnWriteArrayList<ImplThreadLocal<AssetLocator>>();
+    
+    private final HashMap<Class<?>, ImplThreadLocal<AssetLoader>> classToLoaderMap = 
+                new HashMap<Class<?>, ImplThreadLocal<AssetLoader>>();
 
 
-    private final HashMap<String, ImplThreadLocal> loaders =
-                new HashMap<String, ImplThreadLocal>();
+    private final ConcurrentHashMap<String, ImplThreadLocal<AssetLoader>> extensionToLoaderMap =
+                new ConcurrentHashMap<String, ImplThreadLocal<AssetLoader>>();
+    
+    private final ConcurrentHashMap<Class<? extends AssetProcessor>, AssetProcessor> classToProcMap =
+                new ConcurrentHashMap<Class<? extends AssetProcessor>, AssetProcessor>();
+    
+    private final ConcurrentHashMap<Class<? extends AssetCache>, AssetCache> classToCacheMap =
+                new ConcurrentHashMap<Class<? extends AssetCache>, AssetCache>();
 
 
-    public ImplHandler(AssetManager owner){
-        this.owner = owner;
+    public ImplHandler(AssetManager assetManager){
+        this.assetManager = assetManager;
     }
     }
 
 
-    protected class ImplThreadLocal extends ThreadLocal {
+    protected class ImplThreadLocal<T> extends ThreadLocal {
 
 
-        private final Class<?> type;
+        private final Class<T> type;
         private final String path;
         private final String path;
+        private final String[] extensions;
 
 
-        public ImplThreadLocal(Class<?> type){
+        public ImplThreadLocal(Class<T> type, String[] extensions){
             this.type = type;
             this.type = type;
-            path = null;
+            this.extensions = extensions;
+            this.path = null;
         }
         }
 
 
-        public ImplThreadLocal(Class<?> type, String path){
+        public ImplThreadLocal(Class<T> type, String path){
             this.type = type;
             this.type = type;
             this.path = path;
             this.path = path;
+            this.extensions = null;
         }
         }
 
 
+        public ImplThreadLocal(Class<T> type){
+            this.type = type;
+            this.path = null;
+            this.extensions = null;
+        }
+        
         public String getPath() {
         public String getPath() {
             return path;
             return path;
         }
         }
-
+        
+        public String[] getExtensions(){
+            return extensions;
+        }
+        
         public Class<?> getTypeClass(){
         public Class<?> getTypeClass(){
             return type;
             return type;
         }
         }
@@ -137,27 +162,29 @@ public class ImplHandler {
      * access, or null if not found.
      * access, or null if not found.
      */
      */
     public AssetInfo tryLocate(AssetKey key){
     public AssetInfo tryLocate(AssetKey key){
-        synchronized (genericLocators){
-            if (genericLocators.isEmpty())
-                return null;
-
-            for (ImplThreadLocal local : genericLocators){
-                AssetLocator locator = (AssetLocator) local.get();
-                if (local.getPath() != null){
-                    locator.setRootPath((String) local.getPath());
-                }
-                AssetInfo info = locator.locate(owner, key);
-                if (info != null)
-                    return info;
+        if (locatorsList.isEmpty()){
+            logger.warning("There are no locators currently"+
+                            " registered. Use AssetManager."+
+                            "registerLocator() to register a"+
+                            " locator.");
+            return null;
+        }
+        
+        for (ImplThreadLocal local : locatorsList){
+            AssetLocator locator = (AssetLocator) local.get();
+            if (local.getPath() != null){
+                locator.setRootPath((String) local.getPath());
             }
             }
+            AssetInfo info = locator.locate(assetManager, key);
+            if (info != null)
+                return info;
         }
         }
+        
         return null;
         return null;
     }
     }
 
 
     public int getLocatorCount(){
     public int getLocatorCount(){
-        synchronized (genericLocators){
-            return genericLocators.size();
-        }
+        return locatorsList.size();
     }
     }
 
 
     /**
     /**
@@ -166,44 +193,120 @@ public class ImplHandler {
      * @return AssetLoader registered with addLoader.
      * @return AssetLoader registered with addLoader.
      */
      */
     public AssetLoader aquireLoader(AssetKey key){
     public AssetLoader aquireLoader(AssetKey key){
-        synchronized (loaders){
-            ImplThreadLocal local = loaders.get(key.getExtension());
-            if (local != null){
-                AssetLoader loader = (AssetLoader) local.get();
-                return loader;
+        // No need to synchronize() against map, its concurrent
+        ImplThreadLocal local = extensionToLoaderMap.get(key.getExtension());
+        if (local == null){
+            throw new IllegalStateException("No loader registered for type \"" +
+                                        key.getExtension() + "\"");
+
+        }
+        return (AssetLoader) local.get();
+    }
+    
+    public void clearCache(){
+        // The iterator of the values collection is thread safe
+        for (AssetCache cache : classToCacheMap.values()){
+            cache.clearCache();
+        }
+    }
+    
+    public <T extends AssetCache> T getCache(Class<T> cacheClass) {
+        if (cacheClass == null) {
+            return null;
+        }
+        
+        T cache = (T) classToCacheMap.get(cacheClass);
+        if (cache == null) {
+            synchronized (classToCacheMap) {
+                cache = (T) classToCacheMap.get(cacheClass);
+                if (cache == null) {
+                    try {
+                        cache = cacheClass.newInstance();
+                        classToCacheMap.put(cacheClass, cache);
+                    } catch (InstantiationException ex) {
+                        throw new IllegalArgumentException("The cache class cannot"
+                                + " be created, ensure it has empty constructor", ex);
+                    } catch (IllegalAccessException ex) {
+                        throw new IllegalArgumentException("The cache class cannot "
+                                + "be accessed", ex);
+                    }
+                }
             }
             }
+        }
+        return cache;
+    }
+    
+    public <T extends AssetProcessor> T getProcessor(Class<T> procClass){
+        if (procClass == null)
             return null;
             return null;
+        
+        T proc = (T) classToProcMap.get(procClass);
+        if (proc == null){
+            synchronized(classToProcMap){
+                proc = (T) classToProcMap.get(procClass);
+                if (proc == null) {
+                    try {
+                        proc = procClass.newInstance();
+                        classToProcMap.put(procClass, proc);
+                    } catch (InstantiationException ex) {
+                        throw new IllegalArgumentException("The processor class cannot"
+                                + " be created, ensure it has empty constructor", ex);
+                    } catch (IllegalAccessException ex) {
+                        throw new IllegalArgumentException("The processor class cannot "
+                                + "be accessed", ex);
+                    }
+                }
+            }
         }
         }
+        return proc;
     }
     }
-
-    public void addLoader(final Class<?> loaderType, String ... extensions){
-        ImplThreadLocal local = new ImplThreadLocal(loaderType);
+    
+    public void addLoader(final Class<? extends AssetLoader> loaderType, String ... extensions){
+        // Synchronized access must be used for any ops on classToLoaderMap
+        ImplThreadLocal local = new ImplThreadLocal(loaderType, extensions);
         for (String extension : extensions){
         for (String extension : extensions){
             extension = extension.toLowerCase();
             extension = extension.toLowerCase();
-            synchronized (loaders){
-                loaders.put(extension, local);
+            synchronized (classToLoaderMap){
+                classToLoaderMap.put(loaderType, local);
+                extensionToLoaderMap.put(extension, local);
             }
             }
         }
         }
     }
     }
 
 
-    public void addLocator(final Class<?> locatorType, String rootPath){
-        ImplThreadLocal local = new ImplThreadLocal(locatorType, rootPath);
-        synchronized (genericLocators){
-            genericLocators.add(local);
+    public void removeLoader(final Class<? extends AssetLoader> loaderType){
+        // Synchronized access must be used for any ops on classToLoaderMap
+        // Find the loader ImplThreadLocal for this class
+        synchronized (classToLoaderMap){
+            ImplThreadLocal local = classToLoaderMap.get(loaderType);
+            // Remove it from the class->loader map
+            classToLoaderMap.remove(loaderType);
+            // Remove it from the extension->loader map
+            for (String extension : local.getExtensions()){
+                extensionToLoaderMap.remove(extension);
+            }
         }
         }
     }
     }
+    
+    public void addLocator(final Class<? extends AssetLocator> locatorType, String rootPath){
+        locatorsList.add(new ImplThreadLocal(locatorType, rootPath));
+    }
 
 
-    public void removeLocator(final Class<?> locatorType, String rootPath){
-        synchronized (genericLocators){
-            Iterator<ImplThreadLocal> it = genericLocators.iterator();
-            while (it.hasNext()){
-                ImplThreadLocal locator = it.next();
-                if (locator.getPath().equals(rootPath) &&
-                    locator.getTypeClass().equals(locatorType)){
-                    it.remove();
-                }
+    public void removeLocator(final Class<? extends AssetLocator> locatorType, String rootPath){
+        ArrayList<ImplThreadLocal<AssetLocator>> locatorsToRemove = new ArrayList<ImplThreadLocal<AssetLocator>>();
+        Iterator<ImplThreadLocal<AssetLocator>> it = locatorsList.iterator();
+       
+        while (it.hasNext()){
+            ImplThreadLocal locator = it.next();
+            if (locator.getPath().equals(rootPath) &&
+                locator.getTypeClass().equals(locatorType)){
+                //it.remove();
+                // copy on write list doesn't support iterator remove,
+                // must use temporary list
+                locatorsToRemove.add(locator);
             }
             }
         }
         }
+        
+        locatorsList.removeAll(locatorsToRemove);
     }
     }
 
 
 }
 }

+ 47 - 11
engine/src/core/com/jme3/asset/MaterialKey.java

@@ -1,29 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * 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 'jMonkeyEngine' 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.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
 
 
 /**
 /**
- * Used for loading {@link Material materials} only (not material definitions).
- * 
+ * Used for loading {@link Material materials} only (not material definitions!).
+ * Material instances use cloneable smart asset management so that they and any
+ * referenced textures will be collected when all instances of the material
+ * become unreachable.
+ *
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public class MaterialKey extends AssetKey {
-    public MaterialKey(String name){
+public class MaterialKey extends AssetKey<Material> {
+
+    public MaterialKey(String name) {
         super(name);
         super(name);
     }
     }
 
 
-    public MaterialKey(){
+    public MaterialKey() {
         super();
         super();
     }
     }
 
 
     @Override
     @Override
-    public boolean useSmartCache(){
-        return true;
+    public Class<? extends AssetCache> getCacheType() {
+        return WeakRefCloneAssetCache.class;
     }
     }
-    
+
     @Override
     @Override
-    public Object createClonedInstance(Object asset){
-        Material mat = (Material) asset;
-        return mat.clone();
+    public Class<? extends AssetProcessor> getProcessorType() {
+        return CloneableAssetProcessor.class;
     }
     }
 }
 }

+ 14 - 9
engine/src/core/com/jme3/asset/ModelKey.java

@@ -29,33 +29,38 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
-
 package com.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 
 
 /**
 /**
+ * Used to load model files, such as OBJ or Blender models.
+ * This uses cloneable smart asset management, so that when all clones of
+ * this model become unreachable, the original asset is purged from the cache,
+ * allowing textures, materials, shaders, etc referenced by the model to 
+ * become collected.
  * 
  * 
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
 public class ModelKey extends AssetKey<Spatial> {
 public class ModelKey extends AssetKey<Spatial> {
 
 
-    public ModelKey(String name){
+    public ModelKey(String name) {
         super(name);
         super(name);
     }
     }
 
 
-    public ModelKey(){
+    public ModelKey() {
         super();
         super();
     }
     }
+    
     @Override
     @Override
-    public boolean useSmartCache(){
-        return true;
+    public Class<? extends AssetCache> getCacheType(){
+        return WeakRefCloneAssetCache.class;
     }
     }
     
     
     @Override
     @Override
-    public Object createClonedInstance(Object asset){
-        Spatial model = (Spatial) asset;
-        return model.clone();
+    public Class<? extends AssetProcessor> getProcessorType(){
+        return CloneableAssetProcessor.class;
     }
     }
-
 }
 }

+ 70 - 57
engine/src/core/com/jme3/asset/TextureKey.java

@@ -31,15 +31,29 @@
  */
  */
 package com.jme3.asset;
 package com.jme3.asset;
 
 
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.Type;
 import com.jme3.texture.Texture.Type;
-import com.jme3.texture.*;
+import com.jme3.texture.TextureProcessor;
 import java.io.IOException;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 
+/**
+ * Used to load textures from image files such as JPG or PNG. 
+ * Note that texture loaders actually load the asset as an {@link Image}
+ * object, which is then converted to a {@link Texture} in the 
+ * {@link TextureProcessor#postProcess(com.jme3.asset.AssetKey, java.lang.Object) }
+ * method. Since textures are cloneable smart assets, the texture stored
+ * in the cache will be collected when all clones of the texture become
+ * unreachable.
+ * 
+ * @author Kirill Vainer
+ */
 public class TextureKey extends AssetKey<Texture> {
 public class TextureKey extends AssetKey<Texture> {
 
 
     private boolean generateMips;
     private boolean generateMips;
@@ -47,7 +61,7 @@ public class TextureKey extends AssetKey<Texture> {
     private boolean asCube;
     private boolean asCube;
     private boolean asTexture3D;
     private boolean asTexture3D;
     private int anisotropy;
     private int anisotropy;
-    private Texture.Type textureTypeHint=Texture.Type.TwoDimensional;
+    private Texture.Type textureTypeHint = Texture.Type.TwoDimensional;
 
 
     public TextureKey(String name, boolean flipY) {
     public TextureKey(String name, boolean flipY) {
         super(name);
         super(name);
@@ -64,58 +78,19 @@ public class TextureKey extends AssetKey<Texture> {
 
 
     @Override
     @Override
     public String toString() {
     public String toString() {
-        return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
+        return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmapped)" : "");
     }
     }
-
-    /**
-     * Enable smart caching for textures
-     * @return true to enable smart cache
-     */
+    
     @Override
     @Override
-    public boolean useSmartCache() {
-        return true;
+    public Class<? extends AssetCache> getCacheType(){
+        return WeakRefCloneAssetCache.class;
     }
     }
 
 
     @Override
     @Override
-    public Object createClonedInstance(Object asset) {
-        Texture tex = (Texture) asset;
-        return tex.createSimpleClone();
+    public Class<? extends AssetProcessor> getProcessorType(){
+        return TextureProcessor.class;
     }
     }
-
-    @Override
-    public Object postProcess(Object asset) {
-        Image img = (Image) asset;
-        if (img == null) {
-            return null;
-        }
-
-        Texture tex;
-        if (isAsCube()) {
-            if (isFlipY()) {
-                // also flip -y and +y image in cubemap
-                ByteBuffer pos_y = img.getData(2);
-                img.setData(2, img.getData(3));
-                img.setData(3, pos_y);
-            }
-            tex = new TextureCubeMap();
-        } else if (isAsTexture3D()) {
-            tex = new Texture3D();
-        } else {
-            tex = new Texture2D();
-        }
-
-        // enable mipmaps if image has them
-        // or generate them if requested by user
-        if (img.hasMipmaps() || isGenerateMips()) {
-            tex.setMinFilter(Texture.MinFilter.Trilinear);
-        }
-
-        tex.setAnisotropicFilter(getAnisotropy());
-        tex.setName(getName());
-        tex.setImage(img);
-        return tex;
-    }
-
+    
     public boolean isFlipY() {
     public boolean isFlipY() {
         return flipY;
         return flipY;
     }
     }
@@ -152,14 +127,6 @@ public class TextureKey extends AssetKey<Texture> {
         this.asTexture3D = asTexture3D;
         this.asTexture3D = asTexture3D;
     }
     }
 
 
-    @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof TextureKey)) {
-            return false;
-        }
-        return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
-    }
-
     public Type getTextureTypeHint() {
     public Type getTextureTypeHint() {
         return textureTypeHint;
         return textureTypeHint;
     }
     }
@@ -168,7 +135,53 @@ public class TextureKey extends AssetKey<Texture> {
         this.textureTypeHint = textureTypeHint;
         this.textureTypeHint = textureTypeHint;
     }   
     }   
     
     
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final TextureKey other = (TextureKey) obj;
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (this.generateMips != other.generateMips) {
+            return false;
+        }
+        if (this.flipY != other.flipY) {
+            return false;
+        }
+        if (this.asCube != other.asCube) {
+            return false;
+        }
+        if (this.asTexture3D != other.asTexture3D) {
+            return false;
+        }
+        if (this.anisotropy != other.anisotropy) {
+            return false;
+        }
+        if (this.textureTypeHint != other.textureTypeHint) {
+            return false;
+        }
+        return true;
+    }
 
 
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 17 * hash + (super.hashCode());
+        hash = 17 * hash + (this.generateMips ? 1 : 0);
+        hash = 17 * hash + (this.flipY ? 1 : 0);
+        hash = 17 * hash + (this.asCube ? 1 : 0);
+        hash = 17 * hash + (this.asTexture3D ? 1 : 0);
+        hash = 17 * hash + this.anisotropy;
+        hash = 17 * hash + (this.textureTypeHint != null ? this.textureTypeHint.hashCode() : 0);
+        return hash;
+    }
+    
+    @Override
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         OutputCapsule oc = ex.getCapsule(this);

+ 75 - 0
engine/src/core/com/jme3/asset/cache/AssetCache.java

@@ -0,0 +1,75 @@
+package com.jme3.asset.cache;
+
+import com.jme3.asset.AssetKey;
+
+/**
+ * <code>AssetCache</code> is an interface for asset caches. 
+ * Allowing storage of loaded resources in order to improve their access time 
+ * if they are requested again in a short period of time.
+ * Depending on the asset type and how it is used, a specialized 
+ * caching method can be selected that is most appropriate for that asset type.
+ * The asset cache must be thread safe.
+ * <p>
+ * 
+ * 
+ * @author Kirill Vainer
+ */
+public interface AssetCache {
+    /**
+     * Adds an asset to the cache.
+     * Once added, it should be possible to retrieve the asset
+     * by using the {@link #getFromCache(com.jme3.asset.AssetKey) } method.
+     * However the caching criteria may at some point choose that the asset
+     * should be removed from the cache to save memory, in that case, 
+     * {@link #getFromCache(com.jme3.asset.AssetKey) } will return null.
+     * <p><font color="red">Thread-Safe</font>
+     * 
+     * @param <T> The type of the asset to cache.
+     * @param key The asset key that can be used to look up the asset.
+     * @param obj The asset data to cache.
+     */
+    public <T> void addToCache(AssetKey<T> key, T obj);
+    
+    /**
+     * This should be called by the asset manager when it has successfully
+     * acquired a cached asset (with {@link #getFromCache(com.jme3.asset.AssetKey) })
+     * and cloned it for use. 
+     * <p><font color="red">Thread-Safe</font>
+     * 
+     * @param <T> The type of the asset to register.
+     * @param key The asset key of the loaded asset (used to retrieve from cache)
+     * @param clone The <strong>clone</strong> of the asset retrieved from
+     * the cache.
+     */
+    public <T> void registerAssetClone(AssetKey<T> key, T clone);
+    
+    /**
+     * Retrieves an asset from the cache.
+     * It is possible to add an asset to the cache using
+     * {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) }. 
+     * The asset may be removed from the cache automatically even if
+     * it was added previously, in that case, this method will return null.
+     * <p><font color="red">Thread-Safe</font>
+     * 
+     * @param <T> The type of the asset to retrieve
+     * @param key The key used to lookup the asset.
+     * @return The asset that was previously cached, or null if not found.
+     */
+    public <T> T getFromCache(AssetKey<T> key);
+    
+    /**
+     * Deletes an asset from the cache.
+     * <p><font color="red">Thread-Safe</font>
+     * 
+     * @param key The asset key to find the asset to delete.
+     * @return True if the asset was successfully found in the cache
+     * and removed.
+     */
+    public boolean deleteFromCache(AssetKey key);
+    
+    /**
+     * Deletes all assets from the cache.
+     * <p><font color="red">Thread-Safe</font>
+     */
+    public void clearCache();
+}

+ 41 - 0
engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java

@@ -0,0 +1,41 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.asset.cache;
+
+import com.jme3.asset.AssetKey;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * <code>SimpleAssetCache</code> is an asset cache
+ * that caches assets without any automatic removal policy. The user
+ * is expected to manually call {@link #deleteFromCache(com.jme3.asset.AssetKey) }
+ * to delete any assets.
+ * 
+ * @author Kirill Vainer
+ */
+public class SimpleAssetCache implements AssetCache {
+
+    private final ConcurrentHashMap<AssetKey, Object> keyToAssetMap = new ConcurrentHashMap<AssetKey, Object>();
+    
+    public <T> void addToCache(AssetKey<T> key, T obj) {
+        keyToAssetMap.put(key, obj);
+    }
+
+    public <T> void registerAssetClone(AssetKey<T> key, T clone) {
+    }
+
+    public <T> T getFromCache(AssetKey<T> key) {
+        return (T) keyToAssetMap.get(key);
+    }
+
+    public boolean deleteFromCache(AssetKey key) {
+        return keyToAssetMap.remove(key) != null;
+    }
+
+    public void clearCache() {
+        keyToAssetMap.clear();
+    }
+    
+}

+ 88 - 0
engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java

@@ -0,0 +1,88 @@
+package com.jme3.asset.cache;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A garbage collector bound asset cache that handles non-clonable objects.
+ * This cache assumes that the asset given to the user is the same asset
+ * that has been stored in the cache, in other words, 
+ * {@link AssetProcessor#createClone(java.lang.Object) } for that asset
+ * returns the same object as the argument.
+ * This implementation will remove the asset from the cache 
+ * once the asset is no longer referenced in user code and memory is low,
+ * e.g. the VM feels like purging the weak references for that asset.
+ * 
+ * @author Kirill Vainer
+ */
+public class WeakRefAssetCache implements AssetCache {
+
+    private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
+    
+    private final ConcurrentHashMap<AssetKey, AssetRef> assetCache 
+            = new ConcurrentHashMap<AssetKey, AssetRef>();
+    
+    private static class AssetRef extends WeakReference<Object> {
+        
+        private final AssetKey assetKey;
+        
+        public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue<Object> refQueue){
+            super(originalAsset, refQueue);
+            this.assetKey = assetKey;
+        }
+    }
+    
+    private void removeCollectedAssets(){
+        int removedAssets = 0;
+        for (AssetRef ref; (ref = (AssetRef)refQueue.poll()) != null;){
+            // Asset was collected, note that at this point the asset cache 
+            // might not even have this asset anymore, it is OK.
+            if (assetCache.remove(ref.assetKey) != null){
+                removedAssets ++;
+                //System.out.println("WeakRefAssetCache: The asset " + ref.assetKey + " was purged from the cache");
+            }
+        }
+        if (removedAssets >= 1) {
+//            System.out.println("WeakRefAssetCache: " + removedAssets + " assets were purged from the cache.");
+        }
+    }
+    
+    public <T> void addToCache(AssetKey<T> key, T obj) {
+        removeCollectedAssets();
+        
+        // NOTE: Some thread issues can hapen if another
+        // thread is loading an asset with the same key ..
+        AssetRef ref = new AssetRef(key, obj, refQueue);
+        assetCache.put(key, ref);
+        
+//        Texture t = (Texture) obj;
+//        Image i = t.getImage();
+//        System.out.println("add to cache "  + System.identityHashCode(i));
+    }
+
+    public <T> T getFromCache(AssetKey<T> key) {
+        AssetRef ref = assetCache.get(key);
+        if (ref != null){
+            return (T) ref.get();
+        }else{
+            return null;
+        }
+    }
+
+    public boolean deleteFromCache(AssetKey key) {
+        return assetCache.remove(key) != null;
+    }
+
+    public void clearCache() {
+        assetCache.clear();
+    }
+    
+    public <T> void registerAssetClone(AssetKey<T> key, T clone) {
+//        Texture t = (Texture) clone;
+//        System.out.println("clonable asset " + System.identityHashCode(t.getImage()));
+        //throw new UnsupportedOperationException("Cannot use this cache for cloneable assets");
+    }
+}

+ 116 - 0
engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java

@@ -0,0 +1,116 @@
+package com.jme3.asset.cache;
+
+import com.jme3.asset.CloneableSmartAsset;
+import com.jme3.asset.AssetKey;
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.WeakHashMap;
+
+/**
+ * <codeWeakRefCloneAssetCache</code> caches cloneable assets in a weak-key
+ * cache, allowing them to be collected when memory is low.
+ * The cache stores weak references to the asset keys, so that
+ * when all clones of the original asset are collected, will cause the 
+ * asset to be automatically removed from the cache.
+ * 
+* @author Kirill Vainer
+ */
+public class WeakRefCloneAssetCache implements AssetCache {
+
+    private static final class SmartCachedAsset {
+
+        WeakReference<AssetKey> key;
+        CloneableSmartAsset asset;
+
+        public SmartCachedAsset(CloneableSmartAsset originalAsset, AssetKey originalKey) {
+            this.key = new WeakReference<AssetKey>(originalKey);
+            this.asset = originalAsset;
+        }
+    }
+
+    private final WeakHashMap<AssetKey, SmartCachedAsset> smartCache
+            = new WeakHashMap<AssetKey, SmartCachedAsset>();
+    
+    private final ThreadLocal<ArrayDeque<AssetKey>> assetLoadStack 
+            = new ThreadLocal<ArrayDeque<AssetKey>>() {
+        @Override
+        protected ArrayDeque<AssetKey> initialValue() {
+            return new ArrayDeque<AssetKey>();
+        }
+    };
+    
+    public <T> void addToCache(AssetKey<T> key, T obj) {
+        CloneableSmartAsset asset = (CloneableSmartAsset) obj;
+        
+        // No circular references, since the original asset is 
+        // strongly referenced, we don't want the key strongly referenced.
+        asset.setKey(null); 
+        
+        // Place the asset in the cache with a weak ref to the key.
+        synchronized (smartCache) {
+            smartCache.put(key, new SmartCachedAsset(asset, key));
+        }
+        
+        // Push the original key used to load the asset
+        // so that it can be set on the clone later
+        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+        loadStack.push(key);
+    }
+
+    public <T> void registerAssetClone(AssetKey<T> key, T clone) {
+        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+        ((CloneableSmartAsset)clone).setKey(loadStack.pop());
+    }
+
+    public <T> T getFromCache(AssetKey<T> key) {
+        SmartCachedAsset smartInfo;
+        synchronized (smartCache){
+            smartInfo = smartCache.get(key);
+        }
+        
+        if (smartInfo == null) {
+            return null;
+        } else {
+            // NOTE: Optimization so that registerAssetClone()
+            // can check this and determine that the asset clone
+            // belongs to the asset retrieved here.
+            AssetKey keyForTheClone = smartInfo.key.get();
+            if (keyForTheClone == null){
+                // The asset was JUST collected by GC
+                // (between here and smartCache.get)
+                return null;
+            }
+            
+            // Prevent original key from getting collected
+            // while an asset is loaded for it.
+            ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+            loadStack.push(keyForTheClone);
+            
+            return (T) smartInfo.asset;
+        }
+    }
+
+    public boolean deleteFromCache(AssetKey key) {
+        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+        
+        if (!loadStack.isEmpty()){
+            throw new UnsupportedOperationException("Cache cannot be modified"
+                                                  + "while assets are being loaded");
+        }
+        synchronized (smartCache) {
+            return smartCache.remove(key) != null;
+        }
+    }
+    
+    public void clearCache() {
+        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+        
+        if (!loadStack.isEmpty()){
+            throw new UnsupportedOperationException("Cache cannot be modified"
+                                                  + "while assets are being loaded");
+        }
+        synchronized (smartCache) {
+            smartCache.clear();
+        }
+    }
+}

+ 1 - 2
engine/src/core/com/jme3/audio/AudioData.java

@@ -70,7 +70,7 @@ public abstract class AudioData extends NativeObject {
      * @return the duration in seconds of the audio clip.
      * @return the duration in seconds of the audio clip.
      */
      */
     public abstract float getDuration();
     public abstract float getDuration();
-
+    
     /**
     /**
      * @return Bits per single sample from a channel.
      * @return Bits per single sample from a channel.
      */
      */
@@ -106,5 +106,4 @@ public abstract class AudioData extends NativeObject {
         this.bitsPerSample = bitsPerSample;
         this.bitsPerSample = bitsPerSample;
         this.sampleRate = sampleRate;
         this.sampleRate = sampleRate;
     }
     }
-
 }
 }

+ 47 - 3
engine/src/core/com/jme3/audio/AudioKey.java

@@ -33,6 +33,9 @@
 package com.jme3.audio;
 package com.jme3.audio;
 
 
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.WeakRefAssetCache;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.JmeImporter;
@@ -44,7 +47,7 @@ import java.io.IOException;
  *
  *
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public class AudioKey extends AssetKey<AudioData> {
+public class AudioKey extends AssetKey<AudioNode> {
 
 
     private boolean stream;
     private boolean stream;
     private boolean streamCache;
     private boolean streamCache;
@@ -114,10 +117,51 @@ public class AudioKey extends AssetKey<AudioData> {
     }
     }
 
 
     @Override
     @Override
-    public boolean shouldCache(){
-        return !stream && !streamCache;
+    public Class<? extends AssetCache> getCacheType() {
+        if ((stream && streamCache) || !stream) {
+            // Use non-cloning cache
+            return WeakRefAssetCache.class;
+        } else {
+            // Disable caching for streaming audio
+            return null;
+        }
     }
     }
 
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final AudioKey other = (AudioKey) obj;
+        if (!super.equals(other)) {
+            return false;
+        }
+        if (this.stream != other.stream) {
+            return false;
+        }
+        if (this.streamCache != other.streamCache) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 67 * hash + (super.hashCode());
+        hash = 67 * hash + (this.stream ? 1 : 0);
+        hash = 67 * hash + (this.streamCache ? 1 : 0);
+        return hash;
+    }
+    
+    @Override
+    public Class<? extends AssetProcessor> getProcessorType() {
+        return AudioProcessor.class;
+    }
+    
     @Override
     @Override
     public void write(JmeExporter ex) throws IOException{
     public void write(JmeExporter ex) throws IOException{
         super.write(ex);
         super.write(ex);

+ 19 - 0
engine/src/core/com/jme3/audio/AudioProcessor.java

@@ -0,0 +1,19 @@
+package com.jme3.audio;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+
+public class AudioProcessor implements AssetProcessor{
+
+    public Object postProcess(AssetKey key, Object obj) {
+        AudioKey audioKey = (AudioKey) key;
+        AudioData audioData = (AudioData) obj;
+        return new AudioNode(audioData, audioKey);
+    }
+
+    public Object createClone(Object obj) {
+        AudioNode node = (AudioNode) obj;
+        return node.clone();
+    }
+    
+}

+ 2 - 2
engine/src/core/com/jme3/material/Material.java

@@ -29,7 +29,7 @@
  */
  */
 package com.jme3.material;
 package com.jme3.material;
 
 
-import com.jme3.asset.Asset;
+import com.jme3.asset.CloneableSmartAsset;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
 import com.jme3.export.*;
@@ -66,7 +66,7 @@ import java.util.logging.Logger;
  * 
  * 
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public class Material implements Asset, Cloneable, Savable {
+public class Material implements CloneableSmartAsset, Cloneable, Savable {
 
 
     // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
     // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
     public static final int SAVABLE_VERSION = 2;
     public static final int SAVABLE_VERSION = 2;

+ 15 - 0
engine/src/core/com/jme3/material/MaterialProcessor.java

@@ -0,0 +1,15 @@
+package com.jme3.material;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+
+public class MaterialProcessor implements AssetProcessor {
+
+    public Object postProcess(AssetKey key, Object obj) {
+        return null;
+    }
+
+    public Object createClone(Object obj) {
+        return ((Material) obj).clone();
+    }
+}

+ 2 - 2
engine/src/core/com/jme3/scene/Spatial.java

@@ -31,8 +31,8 @@
  */
  */
 package com.jme3.scene;
 package com.jme3.scene;
 
 
-import com.jme3.asset.Asset;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
+import com.jme3.asset.CloneableSmartAsset;
 import com.jme3.bounding.BoundingVolume;
 import com.jme3.bounding.BoundingVolume;
 import com.jme3.collision.Collidable;
 import com.jme3.collision.Collidable;
 import com.jme3.export.*;
 import com.jme3.export.*;
@@ -63,7 +63,7 @@ import java.util.logging.Logger;
  * @author Joshua Slack
  * @author Joshua Slack
  * @version $Revision: 4075 $, $Data$
  * @version $Revision: 4075 $, $Data$
  */
  */
-public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
+public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset {
 
 
     private static final Logger logger = Logger.getLogger(Spatial.class.getName());
     private static final Logger logger = Logger.getLogger(Spatial.class.getName());
 
 

+ 2 - 2
engine/src/core/com/jme3/texture/Texture.java

@@ -34,7 +34,7 @@ package com.jme3.texture;
 
 
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetNotFoundException;
 import com.jme3.asset.AssetNotFoundException;
-import com.jme3.asset.Asset;
+import com.jme3.asset.CloneableSmartAsset;
 import com.jme3.asset.TextureKey;
 import com.jme3.asset.TextureKey;
 import com.jme3.export.*;
 import com.jme3.export.*;
 import com.jme3.util.PlaceholderAssets;
 import com.jme3.util.PlaceholderAssets;
@@ -57,7 +57,7 @@ import java.util.logging.Logger;
  * @author Joshua Slack
  * @author Joshua Slack
  * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
  * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
  */
  */
-public abstract class Texture implements Asset, Savable, Cloneable {
+public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable {
 
 
     public enum Type {
     public enum Type {
 
 

+ 49 - 0
engine/src/core/com/jme3/texture/TextureProcessor.java

@@ -0,0 +1,49 @@
+package com.jme3.texture;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+import com.jme3.asset.TextureKey;
+import java.nio.ByteBuffer;
+
+public class TextureProcessor implements AssetProcessor {
+
+    public Object postProcess(AssetKey key, Object obj) {
+        TextureKey texKey = (TextureKey) key;
+        Image img = (Image) obj;
+        if (img == null) {
+            return null;
+        }
+
+        Texture tex;
+        if (texKey.isAsCube()) {
+            if (texKey.isFlipY()) {
+                // also flip -y and +y image in cubemap
+                ByteBuffer pos_y = img.getData(2);
+                img.setData(2, img.getData(3));
+                img.setData(3, pos_y);
+            }
+            tex = new TextureCubeMap();
+        } else if (texKey.isAsTexture3D()) {
+            tex = new Texture3D();
+        } else {
+            tex = new Texture2D();
+        }
+
+        // enable mipmaps if image has them
+        // or generate them if requested by user
+        if (img.hasMipmaps() || texKey.isGenerateMips()) {
+            tex.setMinFilter(Texture.MinFilter.Trilinear);
+        }
+
+        tex.setAnisotropicFilter(texKey.getAnisotropy());
+        tex.setName(texKey.getName());
+        tex.setImage(img);
+        return tex;
+    }
+
+    public Object createClone(Object obj) {
+        Texture tex = (Texture) obj;
+        return tex.clone();
+    }
+    
+}

+ 120 - 63
engine/src/test/jme3test/asset/TestAssetCache.java

@@ -32,36 +32,43 @@
 
 
 package jme3test.asset;
 package jme3test.asset;
 
 
-import com.jme3.asset.Asset;
-import com.jme3.asset.AssetCache;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetProcessor;
+import com.jme3.asset.CloneableAssetProcessor;
+import com.jme3.asset.CloneableSmartAsset;
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.SimpleAssetCache;
+import com.jme3.asset.cache.WeakRefAssetCache;
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
 public class TestAssetCache {
 public class TestAssetCache {
-    
-    /**
-     * Keep references to loaded assets
-     */
-    private final static boolean KEEP_REFERENCES = false;
-    
+   
     /**
     /**
-     * Enable smart cache use
+     * Counter for asset keys
      */
      */
-    private final static boolean USE_SMART_CACHE = true;
+    private static int counter = 0;
     
     
     /**
     /**
-     * Enable cloneable asset use
+     * Dummy data is an asset having 10 KB to put a dent in the garbage collector
      */
      */
-    private final static boolean CLONEABLE_ASSET = true;
-
-    private static int counter = 0;
-    
-    private static class DummyData implements Asset {
+    private static class DummyData implements CloneableSmartAsset {
 
 
         private AssetKey key;
         private AssetKey key;
-        private byte[] data = new byte[10000];
+        private byte[] data = new byte[10 * 1024];
 
 
+        @Override
+        public Object clone(){
+            try {
+                DummyData clone = (DummyData) super.clone();
+                clone.data = data.clone();
+                return clone;
+            } catch (CloneNotSupportedException ex) {
+                throw new AssertionError();
+            }
+        }
+        
         public byte[] getData(){
         public byte[] getData(){
             return data;
             return data;
         }
         }
@@ -75,67 +82,64 @@ public class TestAssetCache {
         }
         }
     }
     }
     
     
-    private static class SmartKey extends AssetKey {
+    /**
+     * Dummy key is indexed by a generated ID
+     */
+    private static class DummyKey extends AssetKey<DummyData> implements Cloneable {
+        
+        private int id = 0;
+        
+        public DummyKey(){
+            super(".");
+            id = counter++;
+        }
         
         
-        public SmartKey(){
+        public DummyKey(int id){
             super(".");
             super(".");
-            counter++;
+            this.id = id;
         }
         }
         
         
         @Override
         @Override
         public int hashCode(){
         public int hashCode(){
-            return 0;
+            return id;
         }
         }
         
         
         @Override
         @Override
         public boolean equals(Object other){
         public boolean equals(Object other){
-            return false;
+            return ((DummyKey)other).id == id;
         }
         }
         
         
         @Override
         @Override
-        public boolean useSmartCache(){
-            return true;
+        public DummyKey clone(){
+            return new DummyKey(id);
         }
         }
         
         
         @Override
         @Override
-        public Object createClonedInstance(Object asset){
-            DummyData data = new DummyData();
-            return data;
+        public String toString() {
+            return "ID=" + id;
         }
         }
     }
     }
     
     
-    private static class DumbKey extends AssetKey {
-        
-        public DumbKey(){
-            super(".");
-            counter++;
-        }
+    private static void runTest(boolean cloneAssets, boolean smartCache, boolean keepRefs, int limit) {
+        counter = 0;
+        List<Object> refs = new ArrayList<Object>(limit);
         
         
-        @Override
-        public int hashCode(){
-            return 0;
-        }
+        AssetCache cache;
+        AssetProcessor proc = null;
         
         
-        @Override
-        public boolean equals(Object other){
-            return false;
+        if (cloneAssets) {
+            proc = new CloneableAssetProcessor();
         }
         }
         
         
-        @Override
-        public Object createClonedInstance(Object asset){
-            if (CLONEABLE_ASSET){
-                DummyData data = new DummyData();
-                return data;
-            }else{
-                return asset;
+        if (smartCache) {
+            if (cloneAssets) {
+                cache = new WeakRefCloneAssetCache();
+            } else {
+                cache = new WeakRefAssetCache();
             }
             }
+        } else {
+            cache = new SimpleAssetCache();
         }
         }
-    }
-    
-    public static void main(String[] args){
-        List<Object> refs = new ArrayList<Object>(5000);
-        
-        AssetCache cache = new AssetCache();
         
         
         System.gc();
         System.gc();
         System.gc();
         System.gc();
@@ -144,28 +148,81 @@ public class TestAssetCache {
         
         
         long memory = Runtime.getRuntime().freeMemory();
         long memory = Runtime.getRuntime().freeMemory();
         
         
-        while (true){
-            AssetKey key;
+        while (counter < limit){
+            // Create a key
+            DummyKey key = new DummyKey();
+            
+            // Create some data
+            DummyData data = new DummyData();
             
             
-            if (USE_SMART_CACHE){
-                key = new SmartKey();
-            }else{
-                key = new DumbKey();
+            // Post process the data before placing it in the cache
+            if (proc != null){
+                data = (DummyData) proc.postProcess(key, data);
+            }
+            
+            if (data.key != null){
+                // Keeping a hard reference to the key in the cache
+                // means the asset will never be collected => bug
+                throw new AssertionError();
             }
             }
             
             
-            DummyData data = new DummyData();
             cache.addToCache(key, data);
             cache.addToCache(key, data);
             
             
-            if (KEEP_REFERENCES){
+            // Get the asset from the cache
+            AssetKey<DummyData> keyToGet = key.clone();
+            
+            // NOTE: Commented out because getFromCache leaks the original key
+//            DummyData someLoaded = (DummyData) cache.getFromCache(keyToGet);
+//            if (someLoaded != data){
+//                // Failed to get the same asset from the cache => bug
+//                // Since a hard reference to the key is kept, 
+//                // it cannot be collected at this point.
+//                throw new AssertionError();
+//            }
+            
+            // Clone the asset
+            if (proc != null){
+                // Data is now the clone!
+                data = (DummyData) proc.createClone(data);
+                if (smartCache) {
+                    // Registering a clone is only needed
+                    // if smart cache is used.
+                    cache.registerAssetClone(keyToGet, data);
+                    // The clone of the asset must have the same key as the original
+                    // otherwise => bug
+                    if (data.key != key){
+                        throw new AssertionError();
+                    }
+                }
+            }
+            
+            // Keep references to the asset => *should* prevent
+            // collections of the asset in the cache thus causing
+            // an out of memory error.
+            if (keepRefs){
+                // Prevent the saved references from taking too much memory ..
+                if (cloneAssets) {
+                    data.data = null;
+                }
                 refs.add(data);
                 refs.add(data);
             }
             }
             
             
-            if ((counter % 100) == 0){
+            if ((counter % 1000) == 0){
                 long newMem = Runtime.getRuntime().freeMemory();
                 long newMem = Runtime.getRuntime().freeMemory();
                 System.out.println("Allocated objects: " + counter);
                 System.out.println("Allocated objects: " + counter);
-                System.out.println("Allocated memory: " + ((memory - newMem)/1024) + "K" );
+                System.out.println("Allocated memory: " + ((memory - newMem)/(1024*1024)) + " MB" );
                 memory = newMem;
                 memory = newMem;
             }
             }
         }
         }
     }
     }
+    
+    public static void main(String[] args){
+        // Test cloneable smart asset
+        System.out.println("====== Running Cloneable Smart Asset Test ======");
+        runTest(true, true, false, 100000);
+        
+        // Test non-cloneable smart asset
+        System.out.println("====== Running Non-cloneable Smart Asset Test ======");
+        runTest(false, true, false, 100000);
+    }
 }
 }