From 235f8b1cbff8ed13071d5c19c0be492c0b25cb78 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sat, 17 Mar 2012 21:15:49 +0100 Subject: 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. --- src/java/com/jogamp/common/util/IOUtil.java | 250 ++++++++++++++++++---------- 1 file changed, 159 insertions(+), 91 deletions(-) (limited to 'src/java/com/jogamp/common/util') diff --git a/src/java/com/jogamp/common/util/IOUtil.java b/src/java/com/jogamp/common/util/IOUtil.java index a05302a..891453e 100644 --- a/src/java/com/jogamp/common/util/IOUtil.java +++ b/src/java/com/jogamp/common/util/IOUtil.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.AccessControlContext; -import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -48,6 +47,7 @@ import jogamp.common.os.android.StaticContext; import android.content.Context; +import com.jogamp.common.net.AssetURLContext; import com.jogamp.common.nio.Buffers; import com.jogamp.common.os.AndroidVersion; import com.jogamp.common.os.MachineDescription; @@ -68,28 +68,47 @@ public class IOUtil { */ /** - * Copy the specified input stream to the specified output file. The total + * Copy the specified URL resource to the specified output file. The total * number of bytes written is returned. Both streams are closed upon completion. + * + * @param conn the open URLConnection + * @param outFile the destination + * @return + * @throws IOException */ - public static int copyURL2File(URL url, File outFile) throws IOException { - URLConnection conn = url.openConnection(); - conn.connect(); + public static int copyURLConn2File(URLConnection conn, File outFile) throws IOException { + conn.connect(); // redundant int totalNumBytes = 0; InputStream in = new BufferedInputStream(conn.getInputStream()); try { - OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); - try { - totalNumBytes = copyStream2Stream(in, out, conn.getContentLength()); - } finally { - out.close(); - } + totalNumBytes = copyStream2File(in, outFile, conn.getContentLength()); } finally { in.close(); } return totalNumBytes; } + /** + * Copy the specified input stream to the specified output file. The total + * number of bytes written is returned. Both streams are closed upon completion. + * + * @param in the source + * @param outFile the destination + * @param totalNumBytes informal number of expected bytes, maybe used for user feedback while processing. -1 if unknown + * @return + * @throws IOException + */ + public static int copyStream2File(InputStream in, File outFile, int totalNumBytes) throws IOException { + OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); + try { + totalNumBytes = copyStream2Stream(in, out, totalNumBytes); + } finally { + out.close(); + } + return totalNumBytes; + } + /** * Copy the specified input stream to the specified output stream. The total * number of bytes written is returned. @@ -301,103 +320,88 @@ public class IOUtil { */ /** - * Locating a resource using 'getResource(String path, ClassLoader cl)', - * with the + * Locating a resource using {@link #getResource(String, ClassLoader)}: * * + *

+ * Returns the resolved and open URLConnection or null if not found. + *

+ * * @see #getResource(String, ClassLoader) + * @see ClassLoader#getResource(String) + * @see ClassLoader#getSystemResource(String) */ - public static URL getResource(Class context, String resourcePath) { + public static URLConnection getResource(Class context, String resourcePath) { if(null == resourcePath) { return null; } - ClassLoader contextCL = (null!=context)?context.getClassLoader():null; - URL url = null; + ClassLoader contextCL = (null!=context)?context.getClassLoader():IOUtil.class.getClassLoader(); + URLConnection conn = null; if(null != context) { // scoping the path within the class's package String className = context.getName().replace('.', '/'); int lastSlash = className.lastIndexOf('/'); if (lastSlash >= 0) { String tmpPath = className.substring(0, lastSlash + 1) + resourcePath; - url = getResource(tmpPath, contextCL); + conn = getResource(tmpPath, contextCL); } if(DEBUG) { - System.err.println("IOUtil: found <"+resourcePath+"> within class package: "+(null!=url)); + System.err.println("IOUtil: found <"+resourcePath+"> within class package: "+(null!=conn)); } } else if(DEBUG) { System.err.println("IOUtil: null context"); } - if(null == url) { - url = getResource(resourcePath, contextCL); + if(null == conn) { + conn = getResource(resourcePath, contextCL); if(DEBUG) { - System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=url)); + System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=conn)); } } - return url; + return conn; } /** - * Locating a resource using the ClassLoader's facility if not null, - * the absolute URL and absolute file. + * Locating a resource using the ClassLoader's facilities. + *

+ * Returns the resolved and connected URLConnection or null if not found. + *

* * @see ClassLoader#getResource(String) * @see ClassLoader#getSystemResource(String) * @see URL#URL(String) * @see File#File(String) */ - public static URL getResource(String resourcePath, ClassLoader cl) { + public static URLConnection getResource(String resourcePath, ClassLoader cl) { if(null == resourcePath) { return null; } if(DEBUG) { System.err.println("IOUtil: locating <"+resourcePath+">, has cl: "+(null!=cl)); } - URL url = null; - if (cl != null) { - url = cl.getResource(resourcePath); - if(!urlExists(url, "cl.getResource()")) { - url = null; - } - } - if(null == url) { - url = ClassLoader.getSystemResource(resourcePath); - if(!urlExists(url, "cl.getSystemResource()")) { - url = null; - } - } - if(null == url) { + if(resourcePath.startsWith(AssetURLContext.asset_protocol_prefix)) { try { - url = new URL(resourcePath); - if(!urlExists(url, "new URL()")) { - url = null; - } - } catch (Throwable e) { + return AssetURLContext.createURL(resourcePath, cl).openConnection(); + } catch (IOException ioe) { if(DEBUG) { System.err.println("IOUtil: Catched Exception:"); - e.printStackTrace(); - } + ioe.printStackTrace(); + } + return null; } - } - if(null == url) { + } else { try { - File file = new File(resourcePath); - if(file.exists()) { - url = toURLSimple(file); - } - } catch (Throwable e) { + return AssetURLContext.resolve(resourcePath, cl); + } catch (IOException ioe) { if(DEBUG) { System.err.println("IOUtil: Catched Exception:"); - e.printStackTrace(); + ioe.printStackTrace(); } } - if(DEBUG) { - System.err.println("IOUtil: file.exists("+resourcePath+") - "+(null!=url)); - } } - return url; + return null; } /** @@ -411,11 +415,11 @@ public class IOUtil { return null; } - while (baseLocation != null && relativeFile.startsWith("../")) { - baseLocation = baseLocation.getParentFile(); - relativeFile = relativeFile.substring(3); - } if (baseLocation != null) { + while (relativeFile.startsWith("../")) { + baseLocation = baseLocation.getParentFile(); + relativeFile = relativeFile.substring(3); + } final File file = new File(baseLocation, relativeFile); // Handle things on Windows return slashify(file.getPath(), false, false); @@ -424,55 +428,119 @@ public class IOUtil { } /** - * Generates a path for the 'relativeFile' relative to the 'baseLocation'. + * @param path assuming a slashified path beginning with "/" as it's root directory, either denotes a file or directory. + * @return parent of path + * @throws MalformedURLException if path is empty or has parent directory available + */ + public static String getParentOf(String path) throws MalformedURLException { + final int pl = null!=path ? path.length() : 0; + if(pl == 0) { + throw new MalformedURLException("path is empty <"+path+">"); + } + + final int e = path.lastIndexOf("/"); + if( e < 0 ) { + throw new MalformedURLException("path contains no '/' <"+path+">"); + } + if( e == 0 ) { + // path is root directory + throw new MalformedURLException("path has no parents <"+path+">"); + } + if( e < pl - 1 ) { + // path is file, return it's parent directory + return path.substring(0, e+1); + } + final int j = path.lastIndexOf("!") + 1; // '!' Separates JARFile entry -> local start of path + // path is a directory .. + final int p = path.lastIndexOf("/", e-1); + if( p >= j) { + return path.substring(0, p+1); + } + throw new MalformedURLException("parent of path contains no '/' <"+path+">"); + } + + /** + * Generates a path for the 'relativeFile' relative to the 'baseLocation', + * hence the result is a absolute location. * - * @param baseLocation denotes a URL to a file + * @param baseLocation denotes a URL to a directory if ending w/ '/', otherwise we assume a file * @param relativeFile denotes a relative file to the baseLocation's parent directory + * @throws MalformedURLException */ - public static String getRelativeOf(URL baseLocation, String relativeFile) { - String urlPath = baseLocation.getPath(); + public static URL getRelativeOf(URL baseLocation, String relativeFile) throws MalformedURLException { + final String scheme = baseLocation.getProtocol(); + final String auth = baseLocation.getAuthority(); + String path = baseLocation.getPath(); + String query = baseLocation.getQuery(); + String fragment = baseLocation.getRef(); - if ( baseLocation.toString().startsWith("jar") ) { - JarURLConnection jarConnection; - try { - jarConnection = (JarURLConnection) baseLocation.openConnection(); - urlPath = jarConnection.getEntryName(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } + if(!path.endsWith("/")) { + path = getParentOf(path); } - - // Try relative path first - return getRelativeOf(new File(urlPath).getParentFile(), relativeFile); + while (relativeFile.startsWith("../")) { + path = getParentOf(path); + relativeFile = relativeFile.substring(3); + } + return compose(scheme, auth, path, relativeFile, query, fragment); } + public static URL compose(String scheme, String auth, String path1, String path2, String query, String fragment) throws MalformedURLException { + StringBuffer sb = new StringBuffer(); + if(null!=scheme) { + sb.append(scheme); + sb.append(":"); + } + if(null!=auth) { + sb.append("//"); + sb.append(auth); + } + if(null!=path1) { + sb.append(path1); + } + if(null!=path2) { + sb.append(path2); + } + if(null!=query) { + sb.append("?"); + sb.append(query); + } + if(null!=fragment) { + sb.append("#"); + sb.append(fragment); + } + return new URL(sb.toString()); + } + /** - * Returns true, if the URL exists and a connection could be opened. + * Returns the connected URLConnection, or null if not url is not available */ - public static boolean urlExists(URL url) { - return urlExists(url, "."); + public static URLConnection openURL(URL url) { + return openURL(url, "."); } - public static boolean urlExists(URL url, String dbgmsg) { - boolean v = false; + /** + * Returns the connected URLConnection, or null if not url is not available + */ + public static URLConnection openURL(URL url, String dbgmsg) { if(null!=url) { try { - url.openConnection(); - v = true; + final URLConnection c = url.openConnection(); + c.connect(); // redundant if(DEBUG) { System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - true"); } + return c; } catch (IOException ioe) { if(DEBUG) { - System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false: "+ioe.getMessage()); + System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false - "+ioe.getClass().getSimpleName()+": "+ioe.getMessage()); + ioe.printStackTrace(); } } } else if(DEBUG) { System.err.println("IOUtil: no url - urlExists(null) ["+dbgmsg+"]"); } - return v; + return null; } /** @@ -507,7 +575,7 @@ public class IOUtil { * On Android a temp folder relative to the applications local folder * (see {@link Context#getDir(String, int)}) is returned, if * the Android application/activity has registered it's Application Context - * via {@link StaticContext#setContext(Context)}. + * via {@link StaticContext#init(Context, ClassLoader)}. * This allows using the temp folder w/o the need for sdcard * access, which would be the java.io.tempdir location on Android! *

@@ -517,7 +585,7 @@ public class IOUtil { * @throws RuntimeException is the property java.io.tmpdir or the resulting temp directory is invalid * * @see PropertyAccess#getProperty(String, boolean, java.security.AccessControlContext) - * @see StaticContext#setContext(Context) + * @see StaticContext#init(Context, ClassLoader) * @see Context#getDir(String, int) */ public static File getTempRoot(AccessControlContext acc) -- cgit v1.2.3