summaryrefslogtreecommitdiffstats
path: root/src/java/com/jogamp/common/net
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2012-03-17 21:15:49 +0100
committerSven Gothel <[email protected]>2012-03-17 21:15:49 +0100
commit235f8b1cbff8ed13071d5c19c0be492c0b25cb78 (patch)
tree659845e16bd69372bc7ddc3a42b3aa7130d18df5 /src/java/com/jogamp/common/net
parent0cfc7847c58b51c9a26b50d905b592d1fc4c8578 (diff)
Add 'asset' URLConnection; IOUtil uses URLConnection / incr. effeciency; Android ClassLoaderUtil cleanup;
- Add 'asset' URLConnection - Please read API doc 'PiggybackURLConnection' and 'AssetURLConnection' - Solves generic resource handling where platform locations may differ, ie ClassLoader lookup on Android in the 'assets/' subfolder. - New Android 'AssetDexClassLoader' uses 'assets/' folder for findResource(..) - aapt.signed (our APK ant task) - uses 'assets/' folder - adds the 'assetsdir' attribute allowing to copy other assets into the APK - IOUtil uses URLConnection / incr. effeciency - using URLConnection on all getResource(..) since URL is connected anyways for validation and URLConnection can be used by caller right away - String getRelativeOf(URL, String) -> URL getRelativeOf(URL, String) - preserves scheme, authority, etc - simple parentOf handling, more efficient - reusing new 'asset' protocol impl. - Android ClassLoaderUtil cleanup; - Use createClassLoader(..) impl for build-in static jogamp and user APKs, which removes code redundancy Tests: New code path, especially 'assets' are covered by new unit tests, no regressions on Linux.
Diffstat (limited to 'src/java/com/jogamp/common/net')
-rw-r--r--src/java/com/jogamp/common/net/AssetURLConnection.java98
-rw-r--r--src/java/com/jogamp/common/net/AssetURLContext.java212
-rw-r--r--src/java/com/jogamp/common/net/AssetURLStreamHandler.java37
-rw-r--r--src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java66
-rw-r--r--src/java/com/jogamp/common/net/PiggybackURLConnection.java84
-rw-r--r--src/java/com/jogamp/common/net/PiggybackURLContext.java18
-rw-r--r--src/java/com/jogamp/common/net/asset/Handler.java38
7 files changed, 553 insertions, 0 deletions
diff --git a/src/java/com/jogamp/common/net/AssetURLConnection.java b/src/java/com/jogamp/common/net/AssetURLConnection.java
new file mode 100644
index 0000000..f2a5a01
--- /dev/null
+++ b/src/java/com/jogamp/common/net/AssetURLConnection.java
@@ -0,0 +1,98 @@
+package com.jogamp.common.net;
+
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+
+/**
+ * See base class {@link PiggybackURLConnection} for motivation.
+ *
+ * <p>
+ * <i>asset</i> resource location protocol connection.
+ * </p>
+ *
+ * <p>
+ * See {@link AssetURLContext#resolve(String)} how resources are being resolved.
+ * </p>
+ *
+ * <h3>Example:</h3>
+ *
+ * Assuming the plain <i>asset entry</i> <b><code>test/lala.txt</code></b> is being resolved by
+ * a class <code>test.LaLaTest</code>, ie. using the <i>asset aware</i> ClassLoader,
+ * one would use the following <i>asset</i> aware filesystem layout:
+ *
+ * <pre>
+ * test/LaLaTest.class
+ * assets/test/lala.txt
+ * </pre>
+ *
+ * The above maybe on a plain filesystem, or within a JAR or an APK file,
+ * e.g. <code>jogamp.test.apk</code>.
+ *
+ * The above would result in the following possible URLs
+ * reflecting the plain and resolved state of the <i>asset URL</i>:
+ * <pre>
+ * 0 Entry test/lala.txt
+ * 1 Plain asset:test/lala.txt
+ * 2 Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * </pre>
+ *
+ * <p>
+ * The sub protocol URL of the resolved <i>asset</i>
+ * <pre>
+ * 3 Sub-URL jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * </pre>
+ * can be retrieved using {@link #getSubProtocol()}.
+ * </p>
+ *
+ * In all above cases, the <i>asset entry</i> is <b><code>test/lala.txt</code></b>,
+ * which can be retrieved via {@link #getEntryName()}.
+ *
+ * <p>
+ * <h3>General Implementation Notes:</h3>
+ * An <i>asset</i> URL is resolved using {@link AssetURLContext#getClassLoader()}.{@link ClassLoader#getResource(String) getResource(String)},
+ * hence the only requirement for an implementation is to have an <i>asset</i> aware ClassLoader
+ * as described in {@link AssetURLContext#getClassLoader()}.
+ * </p>
+ * <p>
+ * <h3>Warning:</h3>
+ * Since the <i>asset</i> protocol is currently not being implemented
+ * on all platform with an appropriate ClassLoader, a user shall not create the <i>asset</i> URL manually.<br>
+ * </p>
+ *
+ * <h3>Android Implementation Notes:</h3>
+ * <p>
+ * The Android ClassLoader {@link jogamp.android.launcher.AssetDexClassLoader}
+ * resolves the resource as an <i>asset</i> URL in it's {@link ClassLoader#findResource(String)} implementation.</p>
+ * <p>
+ * Currently we attach our <i>asset</i> {@link java.net.URLStreamHandlerFactory}
+ * to allow {@link java.net.URL} to handle <i>asset</i> URLs via our <i>asset</i> {@link java.net.URLStreamHandler} implementation.
+ * </p>
+ */
+public class AssetURLConnection extends PiggybackURLConnection<AssetURLContext> {
+
+ public AssetURLConnection(URL url, AssetURLContext implHelper) {
+ super(url, implHelper);
+ }
+
+ @Override
+ public String getEntryName() throws IOException {
+ if(!connected) {
+ throw new IOException("not connected");
+ }
+
+ final String urlPath ;
+ if(subConn instanceof JarURLConnection) {
+ urlPath = ((JarURLConnection)subConn).getEntryName();
+ } else {
+ urlPath = subConn.getURL().getPath();
+ }
+
+ if(urlPath.startsWith(AssetURLContext.assets_folder)) {
+ return urlPath.substring(AssetURLContext.assets_folder.length());
+ } else {
+ return urlPath;
+ }
+ }
+
+}
diff --git a/src/java/com/jogamp/common/net/AssetURLContext.java b/src/java/com/jogamp/common/net/AssetURLContext.java
new file mode 100644
index 0000000..00c7df7
--- /dev/null
+++ b/src/java/com/jogamp/common/net/AssetURLContext.java
@@ -0,0 +1,212 @@
+package com.jogamp.common.net;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import jogamp.common.Debug;
+
+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 = Debug.isPropertyDefined("jogamp.debug.IOUtil", true);
+
+ /** 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 {
+ // 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: "+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.toURLSimple(file);
+ conn = open(url);
+ type = null != conn ? 3 : -1;
+ }
+ } catch (Throwable e) { if(DEBUG) { System.err.println("ERR: "+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;
+ }
+
+}
diff --git a/src/java/com/jogamp/common/net/AssetURLStreamHandler.java b/src/java/com/jogamp/common/net/AssetURLStreamHandler.java
new file mode 100644
index 0000000..8d95b2d
--- /dev/null
+++ b/src/java/com/jogamp/common/net/AssetURLStreamHandler.java
@@ -0,0 +1,37 @@
+package com.jogamp.common.net;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import com.jogamp.common.net.AssetURLConnection;
+
+/**
+ * {@link URLStreamHandler} to handle the asset protocol.
+ *
+ * <p>
+ * This is the <i>asset</i> URLStreamHandler variation
+ * for manual use.
+ * </p>
+ * <p>
+ * It requires passing a valid {@link AssetURLContext}
+ * for construction, hence it's not suitable for the pkg factory model.
+ * </p>
+ */
+public class AssetURLStreamHandler extends URLStreamHandler {
+ AssetURLContext ctx;
+
+ public AssetURLStreamHandler(AssetURLContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ final AssetURLConnection c = new AssetURLConnection(u, ctx);
+ c.connect();
+ return c;
+ }
+
+
+}
diff --git a/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java b/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java
new file mode 100644
index 0000000..79d7f71
--- /dev/null
+++ b/src/java/com/jogamp/common/net/GenericURLStreamHandlerFactory.java
@@ -0,0 +1,66 @@
+package com.jogamp.common.net;
+
+import java.net.URL;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GenericURLStreamHandlerFactory implements URLStreamHandlerFactory {
+ private static GenericURLStreamHandlerFactory factory = null;
+
+ private final Map<String, URLStreamHandler> protocolHandlers;
+
+ private GenericURLStreamHandlerFactory() {
+ protocolHandlers = new HashMap<String, URLStreamHandler>();
+ }
+
+ /**
+ * Sets the <code>handler</code> for <code>protocol</code>.
+ *
+ * @return the previous set <code>handler</code>, or null if none was set.
+ */
+ public synchronized final URLStreamHandler setHandler(String protocol, URLStreamHandler handler) {
+ return protocolHandlers.put(protocol, handler);
+ }
+
+ /**
+ * Returns the <code>protocol</code> handler previously set via {@link #setHandler(String, URLStreamHandler)},
+ * or null if none was set.
+ */
+ public synchronized final URLStreamHandler getHandler(String protocol) {
+ return protocolHandlers.get(protocol);
+ }
+
+ @Override
+ public synchronized final URLStreamHandler createURLStreamHandler(String protocol) {
+ return getHandler(protocol);
+ }
+
+ /**
+ * Returns the singleton instance of the registered GenericURLStreamHandlerFactory
+ * or null if registration was not successful.
+ * <p>
+ * Registration is only performed once.
+ * </p>
+ */
+ public synchronized static GenericURLStreamHandlerFactory register() {
+ if(null == factory) {
+ factory = AccessController.doPrivileged(new PrivilegedAction<GenericURLStreamHandlerFactory>() {
+ public GenericURLStreamHandlerFactory run() {
+ boolean ok = false;
+ GenericURLStreamHandlerFactory f = new GenericURLStreamHandlerFactory();
+ try {
+ URL.setURLStreamHandlerFactory(f);
+ ok = true;
+ } catch (Throwable e) {
+ System.err.println("GenericURLStreamHandlerFactory: Setting URLStreamHandlerFactory failed: "+e.getMessage());
+ }
+ return ok ? f : null;
+ } } );
+ }
+ return factory;
+ }
+}
diff --git a/src/java/com/jogamp/common/net/PiggybackURLConnection.java b/src/java/com/jogamp/common/net/PiggybackURLConnection.java
new file mode 100644
index 0000000..39f1637
--- /dev/null
+++ b/src/java/com/jogamp/common/net/PiggybackURLConnection.java
@@ -0,0 +1,84 @@
+package com.jogamp.common.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Generic resource location protocol connection,
+ * using another sub-protocol as the vehicle for a piggyback protocol.
+ * <p>
+ * The details of the sub-protocol can be queried using {@link #getSubProtocol()}.
+ * </p>
+ * <p>
+ * See example in {@link AssetURLConnection}.
+ * </p>
+ */
+public abstract class PiggybackURLConnection<I extends PiggybackURLContext> extends URLConnection {
+ protected URL subUrl;
+ protected URLConnection subConn;
+ protected I context;
+
+ /**
+ * @param url the specific URL for this instance
+ * @param context the piggyback context, defining state independent code and constants
+ */
+ protected PiggybackURLConnection(URL url, I context) {
+ super(url);
+ this.context = context;
+ }
+
+ /**
+ * <p>
+ * Resolves the URL via {@link PiggybackURLContext#resolve(String)},
+ * see {@link AssetURLContext#resolve(String)} for an example.
+ * </p>
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void connect() throws IOException {
+ if(!connected) {
+ subConn = context.resolve(url.getPath());
+ subUrl = subConn.getURL();
+ connected = true;
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if(!connected) {
+ throw new IOException("not connected");
+ }
+ return subConn.getInputStream();
+ }
+
+ /**
+ * Returns the <i>entry name</i> of the asset.
+ * <pre>
+ * Plain asset:test/lala.txt
+ * Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * Result test/lala.txt
+ * </pre>
+ * @throws IOException is not connected
+ **/
+ public abstract String getEntryName() throws IOException;
+
+ /**
+ * Returns the resolved <i>sub protocol</i> of the asset or null, ie:
+ * <pre>
+ * Plain asset:test/lala.txt
+ * Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * Result jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt
+ * </pre>
+ *
+ * @throws IOException is not connected
+ */
+ public URL getSubProtocol() throws IOException {
+ if(!connected) {
+ throw new IOException("not connected");
+ }
+ return subUrl;
+ }
+}
diff --git a/src/java/com/jogamp/common/net/PiggybackURLContext.java b/src/java/com/jogamp/common/net/PiggybackURLContext.java
new file mode 100644
index 0000000..2cb48b5
--- /dev/null
+++ b/src/java/com/jogamp/common/net/PiggybackURLContext.java
@@ -0,0 +1,18 @@
+package com.jogamp.common.net;
+
+import java.io.IOException;
+import java.net.URLConnection;
+
+/**
+ * See {@link PiggybackURLConnection} for description and examples.
+ */
+public interface PiggybackURLContext {
+
+ /** Returns the specific protocol, constant for this implementation. */
+ public String getImplementedProtocol();
+
+ /**
+ * Resolving path to a URL sub protocol and return it's open URLConnection
+ **/
+ public URLConnection resolve(String path) throws IOException;
+}
diff --git a/src/java/com/jogamp/common/net/asset/Handler.java b/src/java/com/jogamp/common/net/asset/Handler.java
new file mode 100644
index 0000000..1063797
--- /dev/null
+++ b/src/java/com/jogamp/common/net/asset/Handler.java
@@ -0,0 +1,38 @@
+package com.jogamp.common.net.asset;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import com.jogamp.common.net.AssetURLConnection;
+import com.jogamp.common.net.AssetURLContext;
+
+/**
+ * {@link URLStreamHandler} to handle the asset protocol.
+ *
+ * <p>
+ * This is the <i>asset</i> URLStreamHandler variation
+ * using this class ClassLoader for the pkg factory model.
+ * </p>
+ */
+public class Handler extends URLStreamHandler {
+ static final AssetURLContext localCL = new AssetURLContext() {
+ @Override
+ public ClassLoader getClassLoader() {
+ return Handler.class.getClassLoader();
+ }
+ };
+
+ public Handler() {
+ super();
+ }
+
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ final AssetURLConnection c = new AssetURLConnection(u, localCL);
+ c.connect();
+ return c;
+ }
+
+}