summaryrefslogtreecommitdiffstats
path: root/src/java/com/jogamp/common/net/AssetURLContext.java
blob: 38691e8b2f35a7564e943a2de3c6b6343c13783f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package com.jogamp.common.net;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

import com.jogamp.common.os.AndroidVersion;
import com.jogamp.common.util.IOUtil;

/**
 * See {@link PiggybackURLConnection} for description and examples.
 */
public abstract class AssetURLContext implements PiggybackURLContext {
    private static final boolean DEBUG = IOUtil.DEBUG;

    /** The <i>asset URL</i> protocol name <code>asset</code> */
    public static final String asset_protocol = "asset";

    /** The <i>asset URL</i> protocol prefix <code>asset:</code> */
    public static final String asset_protocol_prefix = "asset:";

    /**
     * The <i>optional</i> <i>asset</i> folder name with ending slash <code>assets/</code>.
     * <p>
     * Note that the <i>asset</i> folder is not used on all platforms using the <i>asset</i> protocol
     * and you should not rely on it, use {@link AssetURLConnection#getEntryName()}.
     * </p>
     **/
    public static final String assets_folder = "assets/";

    public static AssetURLContext create(final ClassLoader cl) {
        return new AssetURLContext() {
            @Override
            public ClassLoader getClassLoader() {
                return cl;
            }
        };
    }

    public static AssetURLStreamHandler createHandler(final ClassLoader cl) {
        return new AssetURLStreamHandler(create(cl));
    }

    /**
     * Create an <i>asset</i> URL, suitable even w/o the registered <i>asset</i> URLStreamHandler.
     * <p>
     * This is equivalent with:
     * <pre>
     *   return new URL(null, path.startsWith("asset:") ? path : "asset:" + path, new AssetURLStreamHandler(cl));
     * </pre>
     * </p>
     * @param path resource path, with or w/o <code>asset:</code> prefix
     * @param cl the ClassLoader used to resolve the location, see {@link #getClassLoader()}.
     * @return
     * @throws MalformedURLException
     */
    public static URL createURL(String path, ClassLoader cl) throws MalformedURLException {
        return new URL(null, path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path, createHandler(cl));
    }

    /**
     * Create an <i>asset</i> URL, suitable only with the registered <i>asset</i> URLStreamHandler.
     * <p>
     * This is equivalent with:
     * <pre>
     *   return new URL(path.startsWith("asset:") ? path : "asset:" + path);
     * </pre>
     * </p>
     * @param path resource path, with or w/o <code>asset:</code> prefix
     * @return
     * @throws MalformedURLException
     */
    public static URL createURL(String path) throws MalformedURLException {
        return new URL(path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path);
    }

    /**
     * Returns the <i>asset</i> handler previously set via {@link #registerHandler(ClassLoader)},
     * or null if none was set.
     */
    public static URLStreamHandler getRegisteredHandler() {
        final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register();
        return ( null != f ) ? f.getHandler(asset_protocol) : null;
    }

    /**
     * Registers the generic URLStreamHandlerFactory via {@link GenericURLStreamHandlerFactory#register()}
     * and if successful sets the <i>asset</i> <code>handler</code> for the given ClassLoader <code>cl</code>.
     *
     * @return true if successful, otherwise false
     */
    public static boolean registerHandler(ClassLoader cl) {
        final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register();
        if( null != f ) {
            f.setHandler(asset_protocol, createHandler(cl));
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns an <i>asset</i> aware ClassLoader.
     * <p>
     * The ClassLoader is required to find the <i>asset</i> resource
     * via it's <code>URL findResource(String)</code> implementation.
     * </p>
     * <p>
     * It's <code>URL findResource(String)</code> implementation shall return either
     * an <i>asset</i> URL <code>asset:sub-protocol</code> or just the sub-protocol URL.
     * </p>
     * <p>
     * For example, on Android, we <i>redirect</i> all <code>path</code> request to <i>assets/</i><code>path</code>.
     * </p>
     */
    public abstract ClassLoader getClassLoader();

    @Override
    public String getImplementedProtocol() {
        return asset_protocol;
    }

    /**
     * {@inheritDoc}
     * <p>
     * This implementation attempts to resolve <code>path</code> in the following order:
     * <ol>
     *   <li> as a valid URL: <code>new URL(path)</code>, use sub-protocol if <i>asset</i> URL</li>
     *   <li> via ClassLoader: {@link #getClassLoader()}.{@link ClassLoader#getResource(String) getResource(path)}, use sub-protocol if <i>asset</i> URL </li>
     *   <li> as a File: <code>new File(path).toURI().toURL()</code>
     * </ol>
     * </p>
     * <p>
     * In case of using the ClassLoader (2) <b>and</b> if running on Android,
     * the {@link #assets_folder} is being prepended to <code>path</code> if missing.
     * </p>
     **/
    @Override
    public URLConnection resolve(String path) throws IOException {
        return resolve(path, getClassLoader());
    }

    public static URLConnection resolve(String path, ClassLoader cl) throws IOException {
        URL url = null;
        URLConnection conn = null;
        int type = -1;

        if(DEBUG) {
            System.err.println("AssetURLContext.resolve: <"+path+">");
        }
        try {
            path = IOUtil.cleanPathString(path);
        } catch (URISyntaxException uriEx) {
            throw new IOException(uriEx);
        }

        try {
            // lookup as valid sub-protocol
            url = new URL(path);
            conn = open(url);
            type = null != conn ? 1 : -1;
        } catch(MalformedURLException e1) { if(DEBUG) { System.err.println("ERR(0): "+e1.getMessage()); } }

        if(null == conn && null != cl) {
            // lookup via ClassLoader .. cleanup leading '/'
            String cpath = path;
            while(cpath.startsWith("/")) {
                cpath = cpath.substring(1);
            }
            if(AndroidVersion.isAvailable) {
                cpath = cpath.startsWith(assets_folder) ? cpath : assets_folder + cpath;
            }
            url = cl.getResource(cpath);
            conn = open(url);
            type = null != conn ? 2 : -1;
        }

        if(null == conn) {
            // lookup as File
            try {
                File file = new File(path);
                if(file.exists()) {
                    url = IOUtil.toURISimple(file).toURL();
                    conn = open(url);
                    type = null != conn ? 3 : -1;
                }
            } catch (Throwable e) { if(DEBUG) { System.err.println("ERR(1): "+e.getMessage()); } }
        }

        if(DEBUG) {
            System.err.println("AssetURLContext.resolve: type "+type+": url <"+url+">, conn <"+conn+">, connURL <"+(null!=conn?conn.getURL():null)+">");
        }
        if(null == conn) {
            throw new FileNotFoundException("Could not look-up: "+path+" as URL, w/ ClassLoader or as File");
        }
        return conn;
    }

    private static URLConnection open(URL url) {
        if(null==url) {
            return null;
        }
        try {
            final URLConnection c = url.openConnection();
            c.connect(); // redundant
            return c;
        } catch (IOException ioe) { if(DEBUG) { System.err.println("ERR: "+ioe.getMessage()); } }
        return null;
    }

}