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(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 <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(final 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(final 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(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("FAIL(1): "+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 = Uri.valueOf(file).toURL(); conn = open(url); type = null != conn ? 3 : -1; } } catch (final Throwable e) { if(DEBUG) { System.err.println("FAIL(3): "+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("FAIL(2): "+ioe.getMessage()); } } return null; } }