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 asset URL protocol name asset */ public static final String asset_protocol = "asset"; /** The asset URL protocol prefix asset: */ public static final String asset_protocol_prefix = "asset:"; /** * The optional asset folder name with ending slash assets/. *

* Note that the asset folder is not used on all platforms using the asset protocol * and you should not rely on it, use {@link AssetURLConnection#getEntryName()}. *

**/ 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 asset URL, suitable even w/o the registered asset URLStreamHandler. *

* This is equivalent with: *

     *   return new URL(null, path.startsWith("asset:") ? path : "asset:" + path, new AssetURLStreamHandler(cl));
     * 
*

* @param path resource path, with or w/o asset: prefix * @param cl the ClassLoader used to resolve the location, see {@link #getClassLoader()}. * @return * @throws MalformedURLException */ public static URL createURL(final String path, final ClassLoader cl) throws MalformedURLException { return new URL(null, path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path, createHandler(cl)); } /** * Create an asset URL, suitable only with the registered asset URLStreamHandler. *

* This is equivalent with: *

     *   return new URL(path.startsWith("asset:") ? path : "asset:" + path);
     * 
*

* @param path resource path, with or w/o asset: prefix * @return * @throws MalformedURLException */ public static URL createURL(final String path) throws MalformedURLException { return new URL(path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path); } /** * Returns the asset 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 asset handler for the given ClassLoader cl. * * @return true if successful, otherwise false */ public static boolean registerHandler(final ClassLoader cl) { final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register(); if( null != f ) { f.setHandler(asset_protocol, createHandler(cl)); return true; } else { return false; } } /** * Returns an asset aware ClassLoader. *

* The ClassLoader is required to find the asset resource * via it's URL findResource(String) implementation. *

*

* It's URL findResource(String) implementation shall return either * an asset URL asset:sub-protocol or just the sub-protocol URL. *

*

* For example, on Android, we redirect all path request to assets/path. *

*/ public abstract ClassLoader getClassLoader(); @Override public String getImplementedProtocol() { return asset_protocol; } /** * {@inheritDoc} *

* This implementation attempts to resolve path in the following order: *

    *
  1. as a valid URL: new URL(path), use sub-protocol if asset URL
  2. *
  3. via ClassLoader: {@link #getClassLoader()}.{@link ClassLoader#getResource(String) getResource(path)}, use sub-protocol if asset URL
  4. *
  5. as a File: new File(path).toURI().toURL() *
*

*

* In case of using the ClassLoader (2) and if running on Android, * the {@link #assets_folder} is being prepended to path if missing. *

**/ @Override public URLConnection resolve(final String path) throws IOException { return resolve(path, getClassLoader()); } public static URLConnection resolve(String path, final 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 (final 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(final 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 { final File file = new File(path); if(file.exists()) { url = IOUtil.toURISimple(file).toURL(); conn = open(url); type = null != conn ? 3 : -1; } } catch (final 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(final URL url) { if(null==url) { return null; } try { final URLConnection c = url.openConnection(); c.connect(); // redundant return c; } catch (final IOException ioe) { if(DEBUG) { System.err.println("ERR: "+ioe.getMessage()); } } return null; } }