1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.asset;
34
35import com.jme3.asset.AssetCache.SmartAssetInfo;
36import com.jme3.audio.AudioData;
37import com.jme3.audio.AudioKey;
38import com.jme3.font.BitmapFont;
39import com.jme3.material.Material;
40import com.jme3.scene.Spatial;
41import com.jme3.shader.Shader;
42import com.jme3.shader.ShaderKey;
43import com.jme3.texture.Texture;
44import java.io.IOException;
45import java.io.InputStream;
46import java.net.URL;
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.Collections;
50import java.util.List;
51import java.util.logging.Level;
52import java.util.logging.Logger;
53
54/**
55 * <code>AssetManager</code> is the primary method for managing and loading
56 * assets inside jME.
57 *
58 * @author Kirill Vainer
59 */
60public class DesktopAssetManager implements AssetManager {
61
62    private static final Logger logger = Logger.getLogger(AssetManager.class.getName());
63
64    private final AssetCache cache = new AssetCache();
65    private final ImplHandler handler = new ImplHandler(this);
66
67    private AssetEventListener eventListener = null;
68    private List<ClassLoader> classLoaders;
69
70//    private final ThreadingManager threadingMan = new ThreadingManager(this);
71//    private final Set<AssetKey> alreadyLoadingSet = new HashSet<AssetKey>();
72
73    public DesktopAssetManager(){
74        this(null);
75    }
76
77    @Deprecated
78    public DesktopAssetManager(boolean loadDefaults){
79        this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"));
80    }
81
82    public DesktopAssetManager(URL configFile){
83        if (configFile != null){
84            InputStream stream = null;
85            try{
86                AssetConfig cfg = new AssetConfig(this);
87                stream = configFile.openStream();
88                cfg.loadText(stream);
89            }catch (IOException ex){
90                logger.log(Level.SEVERE, "Failed to load asset config", ex);
91            }finally{
92                if (stream != null)
93                    try{
94                        stream.close();
95                    }catch (IOException ex){
96                    }
97            }
98        }
99        logger.info("DesktopAssetManager created.");
100    }
101
102    public void addClassLoader(ClassLoader loader){
103        if(classLoaders == null)
104            classLoaders = Collections.synchronizedList(new ArrayList<ClassLoader>());
105        synchronized(classLoaders) {
106            classLoaders.add(loader);
107        }
108    }
109
110    public void removeClassLoader(ClassLoader loader){
111        if(classLoaders != null) synchronized(classLoaders) {
112                classLoaders.remove(loader);
113            }
114    }
115
116    public List<ClassLoader> getClassLoaders(){
117        return classLoaders;
118    }
119
120    public void setAssetEventListener(AssetEventListener listener){
121        eventListener = listener;
122    }
123
124    public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){
125        handler.addLoader(loader, extensions);
126        if (logger.isLoggable(Level.FINER)){
127            logger.log(Level.FINER, "Registered loader: {0} for extensions {1}",
128              new Object[]{loader.getSimpleName(), Arrays.toString(extensions)});
129        }
130    }
131
132    public void registerLoader(String clsName, String ... extensions){
133        Class<? extends AssetLoader> clazz = null;
134        try{
135            clazz = (Class<? extends AssetLoader>) Class.forName(clsName);
136        }catch (ClassNotFoundException ex){
137            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
138        }catch (NoClassDefFoundError ex){
139            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
140        }
141        if (clazz != null){
142            registerLoader(clazz, extensions);
143        }
144    }
145
146    public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){
147        handler.addLocator(locatorClass, rootPath);
148        if (logger.isLoggable(Level.FINER)){
149            logger.log(Level.FINER, "Registered locator: {0}",
150                    locatorClass.getSimpleName());
151        }
152    }
153
154    public void registerLocator(String rootPath, String clsName){
155        Class<? extends AssetLocator> clazz = null;
156        try{
157            clazz = (Class<? extends AssetLocator>) Class.forName(clsName);
158        }catch (ClassNotFoundException ex){
159            logger.log(Level.WARNING, "Failed to find locator: "+clsName, ex);
160        }catch (NoClassDefFoundError ex){
161            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
162        }
163        if (clazz != null){
164            registerLocator(rootPath, clazz);
165        }
166    }
167
168    public void unregisterLocator(String rootPath, Class<? extends AssetLocator> clazz){
169        handler.removeLocator(clazz, rootPath);
170        if (logger.isLoggable(Level.FINER)){
171            logger.log(Level.FINER, "Unregistered locator: {0}",
172                    clazz.getSimpleName());
173        }
174    }
175
176    public void clearCache(){
177        cache.deleteAllAssets();
178    }
179
180    /**
181     * Delete an asset from the cache, returns true if it was deleted
182     * successfully.
183     * <br/><br/>
184     * <font color="red">Thread-safe.</font>
185     */
186    public boolean deleteFromCache(AssetKey key){
187        return cache.deleteFromCache(key);
188    }
189
190    /**
191     * Adds a resource to the cache.
192     * <br/><br/>
193     * <font color="red">Thread-safe.</font>
194     */
195    public void addToCache(AssetKey key, Object asset){
196        cache.addToCache(key, asset);
197    }
198
199    public AssetInfo locateAsset(AssetKey<?> key){
200        if (handler.getLocatorCount() == 0){
201            logger.warning("There are no locators currently"+
202                           " registered. Use AssetManager."+
203                           "registerLocator() to register a"+
204                           " locator.");
205            return null;
206        }
207
208        AssetInfo info = handler.tryLocate(key);
209        if (info == null){
210            logger.log(Level.WARNING, "Cannot locate resource: {0}", key);
211        }
212
213        return info;
214    }
215
216    /**
217     * <font color="red">Thread-safe.</font>
218     *
219     * @param <T>
220     * @param key
221     * @return
222     */
223      public <T> T loadAsset(AssetKey<T> key){
224        if (key == null)
225            throw new IllegalArgumentException("key cannot be null");
226
227        if (eventListener != null)
228            eventListener.assetRequested(key);
229
230        AssetKey smartKey = null;
231        Object o = null;
232        if (key.shouldCache()){
233            if (key.useSmartCache()){
234                SmartAssetInfo smartInfo = cache.getFromSmartCache(key);
235                if (smartInfo != null){
236                    smartKey = smartInfo.smartKey.get();
237                    if (smartKey != null){
238                        o = smartInfo.asset;
239                    }
240                }
241            }else{
242                o = cache.getFromCache(key);
243            }
244        }
245        if (o == null){
246            AssetLoader loader = handler.aquireLoader(key);
247            if (loader == null){
248                throw new IllegalStateException("No loader registered for type \"" +
249                                                key.getExtension() + "\"");
250            }
251
252            if (handler.getLocatorCount() == 0){
253                throw new IllegalStateException("There are no locators currently"+
254                                                " registered. Use AssetManager."+
255                                                "registerLocator() to register a"+
256                                                " locator.");
257            }
258
259            AssetInfo info = handler.tryLocate(key);
260            if (info == null){
261                if (handler.getParentKey() != null && eventListener != null){
262                    // Inform event listener that an asset has failed to load.
263                    // If the parent AssetLoader chooses not to propagate
264                    // the exception, this is the only means of finding
265                    // that something went wrong.
266                    eventListener.assetDependencyNotFound(handler.getParentKey(), key);
267                }
268                throw new AssetNotFoundException(key.toString());
269            }
270
271            try {
272                handler.establishParentKey(key);
273                o = loader.load(info);
274            } catch (IOException ex) {
275                throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);
276            } finally {
277                handler.releaseParentKey(key);
278            }
279            if (o == null){
280                throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using" + loader.getClass().getSimpleName());
281            }else{
282                if (logger.isLoggable(Level.FINER)){
283                    logger.log(Level.FINER, "Loaded {0} with {1}",
284                            new Object[]{key, loader.getClass().getSimpleName()});
285                }
286
287                // do processing on asset before caching
288                o = key.postProcess(o);
289
290                if (key.shouldCache())
291                    cache.addToCache(key, o);
292
293                if (eventListener != null)
294                    eventListener.assetLoaded(key);
295            }
296        }
297
298        // object o is the asset
299        // create an instance for user
300        T clone = (T) key.createClonedInstance(o);
301
302        if (key.useSmartCache()){
303            if (smartKey != null){
304                // smart asset was already cached, use original key
305                ((Asset)clone).setKey(smartKey);
306            }else{
307                // smart asset was cached on this call, use our key
308                ((Asset)clone).setKey(key);
309            }
310        }
311
312        return clone;
313    }
314
315    public Object loadAsset(String name){
316        return loadAsset(new AssetKey(name));
317    }
318
319    /**
320     * Loads a texture.
321     *
322     * @return
323     */
324    public Texture loadTexture(TextureKey key){
325        return (Texture) loadAsset(key);
326    }
327
328    public Material loadMaterial(String name){
329        return (Material) loadAsset(new MaterialKey(name));
330    }
331
332    /**
333     * Loads a texture.
334     *
335     * @param name
336     * @param generateMipmaps Enable if applying texture to 3D objects, disable
337     * for GUI/HUD elements.
338     * @return
339     */
340    public Texture loadTexture(String name, boolean generateMipmaps){
341        TextureKey key = new TextureKey(name, true);
342        key.setGenerateMips(generateMipmaps);
343        key.setAsCube(false);
344        return loadTexture(key);
345    }
346
347    public Texture loadTexture(String name, boolean generateMipmaps, boolean flipY, boolean asCube, int aniso){
348        TextureKey key = new TextureKey(name, flipY);
349        key.setGenerateMips(generateMipmaps);
350        key.setAsCube(asCube);
351        key.setAnisotropy(aniso);
352        return loadTexture(key);
353    }
354
355    public Texture loadTexture(String name){
356        return loadTexture(name, true);
357    }
358
359    public AudioData loadAudio(AudioKey key){
360        return (AudioData) loadAsset(key);
361    }
362
363    public AudioData loadAudio(String name){
364        return loadAudio(new AudioKey(name, false));
365    }
366
367    /**
368     * Loads a bitmap font with the given name.
369     *
370     * @param name
371     * @return
372     */
373    public BitmapFont loadFont(String name){
374        return (BitmapFont) loadAsset(new AssetKey(name));
375    }
376
377    public InputStream loadGLSLLibrary(AssetKey key){
378        return (InputStream) loadAsset(key);
379    }
380
381    /**
382     * Load a vertex/fragment shader combo.
383     *
384     * @param key
385     * @return
386     */
387    public Shader loadShader(ShaderKey key){
388        // cache abuse in method
389        // that doesn't use loaders/locators
390        Shader s = (Shader) cache.getFromCache(key);
391        if (s == null){
392            String vertName = key.getVertName();
393            String fragName = key.getFragName();
394
395            String vertSource = (String) loadAsset(new AssetKey(vertName));
396            String fragSource = (String) loadAsset(new AssetKey(fragName));
397
398            s = new Shader(key.getLanguage());
399            s.addSource(Shader.ShaderType.Vertex,   vertName, vertSource, key.getDefines().getCompiled());
400            s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());
401
402            cache.addToCache(key, s);
403        }
404        return s;
405    }
406
407    public Spatial loadModel(ModelKey key){
408        return (Spatial) loadAsset(key);
409    }
410
411    /**
412     * Load a model.
413     *
414     * @param name
415     * @return
416     */
417    public Spatial loadModel(String name){
418        return loadModel(new ModelKey(name));
419    }
420
421}
422