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.export.*;
36import java.io.IOException;
37import java.util.LinkedList;
38
39/**
40 * <code>AssetKey</code> is a key that is used to
41 * look up a resource from a cache.
42 * This class should be immutable.
43 */
44public class AssetKey<T> implements Savable {
45
46    protected String name;
47    protected transient String folder;
48    protected transient String extension;
49
50    public AssetKey(String name){
51        this.name = reducePath(name);
52        this.extension = getExtension(this.name);
53    }
54
55    public AssetKey(){
56    }
57
58    protected static String getExtension(String name){
59        int idx = name.lastIndexOf('.');
60        //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
61        if (name.toLowerCase().endsWith(".xml")) {
62            idx = name.substring(0, idx).lastIndexOf('.');
63            if (idx == -1) {
64                idx = name.lastIndexOf('.');
65            }
66        }
67        if (idx <= 0 || idx == name.length() - 1)
68            return "";
69        else
70            return name.substring(idx+1).toLowerCase();
71    }
72
73    protected static String getFolder(String name){
74        int idx = name.lastIndexOf('/');
75        if (idx <= 0 || idx == name.length() - 1)
76            return "";
77        else
78            return name.substring(0, idx+1);
79    }
80
81    public String getName() {
82        return name;
83    }
84
85    /**
86     * @return The extension of the <code>AssetKey</code>'s name. For example,
87     * the name "Interface/Logo/Monkey.png" has an extension of "png".
88     */
89    public String getExtension() {
90        return extension;
91    }
92
93    public String getFolder(){
94        if (folder == null)
95            folder = getFolder(name);
96
97        return folder;
98    }
99
100    /**
101     * Do any post-processing on the resource after it has been loaded.
102     * @param asset
103     */
104    public Object postProcess(Object asset){
105        return asset;
106    }
107
108    /**
109     * Create a new instance of the asset, based on a prototype that is stored
110     * in the cache. Implementations are allowed to return the given parameter
111     * as-is if it is considered that cloning is not necessary for that particular
112     * asset type.
113     *
114     * @param asset The asset to be cloned.
115     * @return The asset, possibly cloned.
116     */
117    public Object createClonedInstance(Object asset){
118        return asset;
119    }
120
121    /**
122     * @return True if the asset for this key should be cached. Subclasses
123     * should override this method if they want to override caching behavior.
124     */
125    public boolean shouldCache(){
126        return true;
127    }
128
129    /**
130     * @return Should return true, if the asset objects implement the "Asset"
131     * interface and want to be removed from the cache when no longer
132     * referenced in user-code.
133     */
134    public boolean useSmartCache(){
135        return false;
136    }
137
138    /**
139     * Removes all relative elements of a path (A/B/../C.png and A/./C.png).
140     * @param path The path containing relative elements
141     * @return A path without relative elements
142     */
143    public static String reducePath(String path) {
144        if (path == null || path.indexOf("./") == -1) {
145            return path;
146        }
147        String[] parts = path.split("/");
148        LinkedList<String> list = new LinkedList<String>();
149        for (int i = 0; i < parts.length; i++) {
150            String string = parts[i];
151            if (string.length() == 0 || string.equals(".")) {
152                //do nothing
153            } else if (string.equals("..")) {
154                if (list.size() > 0) {
155                    list.removeLast();
156                } else {
157                    throw new IllegalStateException("Relative path is outside assetmanager root!");
158                }
159            } else {
160                list.add(string);
161            }
162        }
163        StringBuilder builder = new StringBuilder();
164        for (int i = 0; i < list.size(); i++) {
165            String string = list.get(i);
166            if (i != 0) {
167                builder.append("/");
168            }
169            builder.append(string);
170        }
171        return builder.toString();
172    }
173
174    @Override
175    public boolean equals(Object other){
176        if (!(other instanceof AssetKey)){
177            return false;
178        }
179        return name.equals(((AssetKey)other).name);
180    }
181
182    @Override
183    public int hashCode(){
184        return name.hashCode();
185    }
186
187    @Override
188    public String toString(){
189        return name;
190    }
191
192    public void write(JmeExporter ex) throws IOException {
193        OutputCapsule oc = ex.getCapsule(this);
194        oc.write(name, "name", null);
195    }
196
197    public void read(JmeImporter im) throws IOException {
198        InputCapsule ic = im.getCapsule(this);
199        name = reducePath(ic.readString("name", null));
200        extension = getExtension(name);
201    }
202
203}
204