jaulib v1.3.6
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
JarUtil.java
Go to the documentation of this file.
1/**
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2011 Gothel Software e.K.
5 * Copyright (c) 2011 JogAmp Community.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 */
26package org.jau.pkg;
27
28import java.io.BufferedInputStream;
29import java.io.BufferedOutputStream;
30import java.io.File;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.net.JarURLConnection;
36import java.net.URISyntaxException;
37import java.net.URL;
38import java.net.URLConnection;
39import java.security.cert.Certificate;
40import java.util.Enumeration;
41import java.util.HashMap;
42import java.util.Map;
43import java.util.jar.JarEntry;
44import java.util.jar.JarFile;
45
46import org.jau.io.IOUtil;
47import org.jau.net.Uri;
48import org.jau.sec.SecurityUtil;
49import org.jau.sys.Debug;
50import org.jau.sys.JNILibrary;
51import org.jau.sys.PlatformProps;
52import org.jau.sys.PlatformTypes;
53
54public class JarUtil {
55 private static final boolean DEBUG = Debug.debug("JarUtil");
56
57 private static final int BUFFER_SIZE = 4096;
58
59 /**
60 * Interface allowing users to provide an URL resolver that will convert custom classloader
61 * URLs like Eclipse/OSGi <i>bundleresource:</i> URLs to normal <i>jar:</i> URLs.
62 * <p>
63 * This might be required for custom classloader where the URI protocol is unknown
64 * to the standard runtime environment.
65 * </p>
66 * <p>
67 * Note: The provided resolver is only utilized if a given URI's protocol could not be resolved.
68 * I.e. it will not be invoked for known protocols like <i>http</i>, <i>https</i>, <i>jar</i> or <i>file</i>.
69 * </p>
70 */
71 public interface Resolver {
72 URL resolve(URL url);
73 }
74
75 private static Resolver resolver;
76
77 /**
78 * Setting a custom {@link Resolver} instance.
79 *
80 * @param r {@link Resolver} to use after querying class file URLs from the classloader.
81 * @throws IllegalArgumentException if the passed resolver is <code>null</code>
82 * @throws IllegalStateException if the resolver has already been set.
83 * @throws SecurityException if the security manager doesn't have the setFactory
84 * permission
85 */
86 @SuppressWarnings({ "removal" })
87 public static void setResolver(final Resolver r) throws IllegalArgumentException, IllegalStateException, SecurityException {
88 if(r == null) {
89 throw new IllegalArgumentException("Null Resolver passed");
90 }
91
92 if(resolver != null) {
93 throw new IllegalStateException("Resolver already set!");
94 }
95
96 final SecurityManager security = SecurityUtil.getSecurityManager();
97 if(security != null) {
98 security.checkSetFactory();
99 }
100
101 resolver = r;
102 }
103
104 /**
105 * Returns <code>true</code> if the Class's <code>"com.jogamp.common.GlueGenVersion"</code>
106 * is loaded from a JarFile and hence has a Jar URI like
107 * URI <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code>.
108 * <p>
109 * <i>sub_protocol</i> may be "file", "http", etc..
110 * </p>
111 *
112 * @param clazzBinName "com.jogamp.common.GlueGenVersion"
113 * @param cl
114 * @return true if the class is loaded from a Jar file, otherwise false.
115 * @see {@link #getJarUri(String, ClassLoader)}
116 */
117 public static boolean hasJarUri(final String clazzBinName, final ClassLoader cl) {
118 try {
119 return null != getJarUri(clazzBinName, cl);
120 } catch (final Exception e) { /* ignore */ }
121 return false;
122 }
123
124 /**
125 * The Class's <code>"com.jogamp.common.GlueGenVersion"</code>
126 * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code>
127 * will be returned.
128 * <p>
129 * <i>sub_protocol</i> may be "file", "http", etc..
130 * </p>
131 *
132 * @param clazzBinName "com.jogamp.common.GlueGenVersion"
133 * @param cl ClassLoader to locate the JarFile
134 * @return "jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"
135 * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or null arguments
136 * @throws IOException if the class's Jar file could not been found by the ClassLoader
137 * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri
138 * @see {@link IOUtil#getClassURL(String, ClassLoader)}
139 */
140 public static Uri getJarUri(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException {
141 if(null == clazzBinName || null == cl) {
142 throw new IllegalArgumentException("null arguments: clazzBinName "+clazzBinName+", cl "+cl);
143 }
144 final Uri uri;
145 final URL url;
146 {
147 url = IOUtil.getClassURL(clazzBinName, cl);
148 final String scheme = url.getProtocol();
149 if( null != resolver &&
150 !scheme.equals( Uri.JAR_SCHEME ) &&
151 !scheme.equals( Uri.FILE_SCHEME ) &&
152 !scheme.equals( Uri.HTTP_SCHEME ) &&
153 !scheme.equals( Uri.HTTPS_SCHEME ) )
154 {
155 final URL _url = resolver.resolve( url );
156 uri = Uri.valueOf(_url);
157 if(DEBUG) {
158 System.err.println("getJarUri Resolver: "+url+"\n\t-> "+_url+"\n\t-> "+uri);
159 }
160 } else {
161 uri = Uri.valueOf(url);
162 if(DEBUG) {
163 System.err.println("getJarUri Default "+url+"\n\t-> "+uri);
164 }
165 }
166 }
167 if( !uri.isJarScheme() ) {
168 throw new IllegalArgumentException("Uri is not using scheme "+Uri.JAR_SCHEME+": <"+uri+">");
169 }
170 if(DEBUG) {
171 System.err.println("getJarUri res: "+clazzBinName+" -> "+url+" -> "+uri);
172 }
173 return uri;
174 }
175
176
177 /**
178 * The Class's Jar Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code>
179 * Jar basename <code>gluegen-rt.jar</code> will be returned.
180 * <p>
181 * <i>sub_protocol</i> may be "file", "http", etc..
182 * </p>
183 *
184 * @param classJarUri as retrieved w/ {@link #getJarUri(String, ClassLoader) getJarUri("com.jogamp.common.GlueGenVersion", cl)},
185 * i.e. <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code>
186 * @return <code>gluegen-rt.jar</code>
187 * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or is null
188 * @see {@link IOUtil#getClassURL(String, ClassLoader)}
189 */
190 public static Uri.Encoded getJarBasename(final Uri classJarUri) throws IllegalArgumentException {
191 if(null == classJarUri) {
192 throw new IllegalArgumentException("Uri is null");
193 }
194 if( !classJarUri.isJarScheme() ) {
195 throw new IllegalArgumentException("Uri is not using scheme "+Uri.JAR_SCHEME+": <"+classJarUri+">");
196 }
197 Uri.Encoded ssp = classJarUri.schemeSpecificPart;
198
199 // from
200 // file:/some/path/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class
201 // to
202 // file:/some/path/gluegen-rt.jar
203 int idx = ssp.lastIndexOf(Uri.JAR_SCHEME_SEPARATOR);
204 if (0 <= idx) {
205 ssp = ssp.substring(0, idx); // exclude '!/'
206 } else {
207 throw new IllegalArgumentException("Uri does not contain jar uri terminator '!', in <"+classJarUri+">");
208 }
209
210 // from
211 // file:/some/path/gluegen-rt.jar
212 // to
213 // gluegen-rt.jar
214 idx = ssp.lastIndexOf('/');
215 if(0 > idx) {
216 // no abs-path, check for protocol terminator ':'
217 idx = ssp.lastIndexOf(':');
218 if(0 > idx) {
219 throw new IllegalArgumentException("Uri does not contain protocol terminator ':', in <"+classJarUri+">");
220 }
221 }
222 ssp = ssp.substring(idx+1); // just the jar name
223
224 if(0 >= ssp.lastIndexOf(".jar")) {
225 throw new IllegalArgumentException("No Jar name in <"+classJarUri+">");
226 }
227 if(DEBUG) {
228 System.err.println("getJarName res: "+ssp);
229 }
230 return ssp;
231 }
232
233 /**
234 * The Class's <code>com.jogamp.common.GlueGenVersion</code>
235 * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code>
236 * Jar basename <code>gluegen-rt.jar</code> will be returned.
237 * <p>
238 * <i>sub_protocol</i> may be "file", "http", etc..
239 * </p>
240 *
241 * @param clazzBinName <code>com.jogamp.common.GlueGenVersion</code>
242 * @param cl
243 * @return <code>gluegen-rt.jar</code>
244 * @throws IllegalArgumentException if the Uri doesn't match the expected formatting
245 * @throws IOException if the class's Jar file could not been found by the ClassLoader.
246 * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri
247 * @see {@link IOUtil#getClassURL(String, ClassLoader)}
248 */
249 public static Uri.Encoded getJarBasename(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException {
250 return getJarBasename( getJarUri(clazzBinName, cl) );
251 }
252
253 /**
254 * The Class's Jar Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code>
255 * Jar file's entry <code>/com/jogamp/common/GlueGenVersion.class</code> will be returned.
256 *
257 * @param classJarUri as retrieved w/ {@link #getJarUri(String, ClassLoader) getJarUri("com.jogamp.common.GlueGenVersion", cl)},
258 * i.e. <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code>
259 * @return <code>/com/jogamp/common/GlueGenVersion.class</code>
260 * @see {@link IOUtil#getClassURL(String, ClassLoader)}
261 */
262 public static Uri.Encoded getJarEntry(final Uri classJarUri) {
263 if(null == classJarUri) {
264 throw new IllegalArgumentException("Uri is null");
265 }
266 if( !classJarUri.isJarScheme() ) {
267 throw new IllegalArgumentException("Uri is not a using scheme "+Uri.JAR_SCHEME+": <"+classJarUri+">");
268 }
269 final Uri.Encoded uriSSP = classJarUri.schemeSpecificPart;
270
271 // from
272 // file:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class
273 // to
274 // /com/jogamp/common/GlueGenVersion.class
275 final int idx = uriSSP.lastIndexOf(Uri.JAR_SCHEME_SEPARATOR);
276 if (0 <= idx) {
277 final Uri.Encoded res = uriSSP.substring(idx+1); // right of '!'
278 // Uri TODO ? final String res = Uri.decode(uriSSP.substring(idx+1)); // right of '!'
279 if(DEBUG) {
280 System.err.println("getJarEntry res: "+classJarUri+" -> "+uriSSP+" -> "+idx+" -> "+res);
281 }
282 return res;
283 } else {
284 throw new IllegalArgumentException("JAR Uri does not contain jar uri terminator '!', uri <"+classJarUri+">");
285 }
286 }
287
288 /**
289 * The Class's <code>"com.jogamp.common.GlueGenVersion"</code>
290 * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code>
291 * Jar file Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/</code> will be returned.
292 * <p>
293 * <i>sub_protocol</i> may be "file", "http", etc..
294 * </p>
295 *
296 * @param clazzBinName "com.jogamp.common.GlueGenVersion"
297 * @param cl
298 * @return "jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/"
299 * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or null arguments
300 * @throws IOException if the class's Jar file could not been found by the ClassLoader
301 * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri
302 * @see {@link IOUtil#getClassURL(String, ClassLoader)}
303 */
304 public static Uri getJarFileUri(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException {
305 if(null == clazzBinName || null == cl) {
306 throw new IllegalArgumentException("null arguments: clazzBinName "+clazzBinName+", cl "+cl);
307 }
308 final Uri jarSubUri = getJarUri(clazzBinName, cl).getContainedUri();
309 final Uri uri = Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUri.toString()+"!/");
310 if(DEBUG) {
311 System.err.println("getJarFileUri res: "+uri);
312 }
313 return uri;
314 }
315
316 /**
317 * @param baseUri file:/some/path/
318 * @param jarFileName gluegen-rt.jar (Uri encoded)
319 * @return jar:file:/some/path/gluegen-rt.jar!/
320 * @throws URISyntaxException
321 * @throws IllegalArgumentException null arguments
322 */
323 public static Uri getJarFileUri(final Uri baseUri, final Uri.Encoded jarFileName) throws IllegalArgumentException, URISyntaxException {
324 if(null == baseUri || null == jarFileName) {
325 throw new IllegalArgumentException("null arguments: baseUri "+baseUri+", jarFileName "+jarFileName);
326 }
327 return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+baseUri.toString()+jarFileName+"!/");
328 }
329
330 /**
331 * @param jarSubUri file:/some/path/gluegen-rt.jar
332 * @return jar:file:/some/path/gluegen-rt.jar!/
333 * @throws IllegalArgumentException null arguments
334 * @throws URISyntaxException
335 */
336 public static Uri getJarFileUri(final Uri jarSubUri) throws IllegalArgumentException, URISyntaxException {
337 if(null == jarSubUri) {
338 throw new IllegalArgumentException("jarSubUri is null");
339 }
340 return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUri.toString()+"!/");
341 }
342
343 /**
344 * @param jarSubUriS file:/some/path/gluegen-rt.jar (Uri encoded)
345 * @return jar:file:/some/path/gluegen-rt.jar!/
346 * @throws IllegalArgumentException null arguments
347 * @throws URISyntaxException
348 */
349 public static Uri getJarFileUri(final Uri.Encoded jarSubUriS) throws IllegalArgumentException, URISyntaxException {
350 if(null == jarSubUriS) {
351 throw new IllegalArgumentException("jarSubUriS is null");
352 }
353 return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUriS+"!/");
354 }
355
356 /**
357 * @param jarFileUri jar:file:/some/path/gluegen-rt.jar!/
358 * @param jarEntry com/jogamp/common/GlueGenVersion.class
359 * @return jar:file:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class
360 * @throws IllegalArgumentException null arguments
361 * @throws URISyntaxException
362 */
363 public static Uri getJarEntryUri(final Uri jarFileUri, final Uri.Encoded jarEntry) throws IllegalArgumentException, URISyntaxException {
364 if(null == jarEntry) {
365 throw new IllegalArgumentException("jarEntry is null");
366 }
367 return Uri.cast(jarFileUri.toString()+jarEntry);
368 }
369
370 /**
371 * @param clazzBinName com.jogamp.common.util.cache.TempJarCache
372 * @param cl domain
373 * @return JarFile containing the named class within the given ClassLoader
374 * @throws IOException if the class's Jar file could not been found by the ClassLoader
375 * @throws IllegalArgumentException null arguments
376 * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri
377 * @see {@link #getJarFileUri(String, ClassLoader)}
378 */
379 public static JarFile getJarFile(final String clazzBinName, final ClassLoader cl) throws IOException, IllegalArgumentException, URISyntaxException {
380 return getJarFile( getJarFileUri(clazzBinName, cl) );
381 }
382
383 /**
384 * @param jarFileUri jar:file:/some/path/gluegen-rt.jar!/
385 * @return JarFile as named by Uri within the given ClassLoader
386 * @throws IllegalArgumentException null arguments
387 * @throws IOException if the Jar file could not been found
388 * @throws URISyntaxException
389 */
390 public static JarFile getJarFile(final Uri jarFileUri) throws IOException, IllegalArgumentException, URISyntaxException {
391 if(null == jarFileUri) {
392 throw new IllegalArgumentException("null jarFileUri");
393 }
394 if(DEBUG) {
395 System.err.println("getJarFile.0: "+jarFileUri.toString());
396 }
397 final URL jarFileURL = jarFileUri.toURL();
398 if(DEBUG) {
399 System.err.println("getJarFile.1: "+jarFileURL.toString());
400 }
401 final URLConnection urlc = jarFileURL.openConnection();
402 if(urlc instanceof JarURLConnection) {
403 final JarURLConnection jarConnection = (JarURLConnection)jarFileURL.openConnection();
404 final JarFile jarFile = jarConnection.getJarFile();
405 if(DEBUG) {
406 System.err.println("getJarFile res: "+jarFile.getName());
407 }
408 return jarFile;
409 }
410 if(DEBUG) {
411 System.err.println("getJarFile res: NULL");
412 }
413 return null;
414 }
415
416 /**
417 * Locates the {@link JarUtil#getJarFileUri(Uri) Jar file Uri} of a given resource
418 * relative to a given class's Jar's Uri.
419 * <pre>
420 * class's jar url path + cutOffInclSubDir + relResPath,
421 * </pre>
422 * Example #1
423 * <pre>
424 * classFromJavaJar = com.lighting.Test (in: file:/storage/TestLighting.jar)
425 * cutOffInclSubDir = lights/
426 * relResPath = LightAssets.jar
427 * Result : file:/storage/lights/LightAssets.jar
428 * </pre>
429 * Example #2
430 * <pre>
431 * classFromJavaJar = com.lighting.Test (in: file:/storage/lights/TestLighting.jar)
432 * cutOffInclSubDir = lights/
433 * relResPath = LightAssets.jar
434 * Result : file:/storage/lights/LightAssets.jar
435 * </pre>
436 *
437 * TODO: Enhance documentation!
438 *
439 * @param classFromJavaJar Used to get the root Uri for the class's Jar Uri.
440 * @param cutOffInclSubDir The <i>cut off</i> included sub-directory prepending the relative resource path.
441 * If the root Uri includes cutOffInclSubDir, it is no more added to the result.
442 * @param relResPath The relative resource path. (Uri encoded)
443 * @return The resulting resource Uri, which is not tested.
444 * @throws IllegalArgumentException
445 * @throws IOException
446 * @throws URISyntaxException
447 */
448 public static Uri getRelativeOf(final Class<?> classFromJavaJar, final Uri.Encoded cutOffInclSubDir, final Uri.Encoded relResPath) throws IllegalArgumentException, IOException, URISyntaxException {
449 final ClassLoader cl = classFromJavaJar.getClassLoader();
450 final Uri classJarUri = JarUtil.getJarUri(classFromJavaJar.getName(), cl);
451 if( DEBUG ) {
452 System.err.println("JarUtil.getRelativeOf: "+"(classFromJavaJar "+classFromJavaJar+", classJarUri "+classJarUri+
453 ", cutOffInclSubDir "+cutOffInclSubDir+", relResPath "+relResPath+"): ");
454 }
455
456 final Uri jarSubUri = classJarUri.getContainedUri();
457 if(null == jarSubUri) {
458 throw new IllegalArgumentException("JarSubUri is null of: "+classJarUri);
459 }
460 final Uri.Encoded jarUriRoot = jarSubUri.getDirectory().getEncoded();
461 if( DEBUG ) {
462 System.err.println("JarUtil.getRelativeOf: "+"uri "+jarSubUri.toString()+" -> "+jarUriRoot);
463 }
464
465 final Uri.Encoded resUri;
466 if( jarUriRoot.endsWith(cutOffInclSubDir.get()) ) {
467 resUri = jarUriRoot.concat(relResPath);
468 } else {
469 resUri = jarUriRoot.concat(cutOffInclSubDir).concat(relResPath);
470 }
471 if( DEBUG ) {
472 System.err.println("JarUtil.getRelativeOf: "+"... -> "+resUri);
473 }
474
475 final Uri resJarUri = JarUtil.getJarFileUri(resUri);
476 if( DEBUG ) {
477 System.err.println("JarUtil.getRelativeOf: "+"fin "+resJarUri);
478 }
479 return resJarUri;
480 }
481
482 /**
483 * Return a map from native-lib-base-name to entry-name.
484 */
485 public static Map<String, String> getNativeLibNames(final JarFile jarFile) {
486 if (DEBUG) {
487 System.err.println("JarUtil: getNativeLibNames: "+jarFile);
488 }
489
490 final Map<String,String> nameMap = new HashMap<String, String>();
491 final Enumeration<JarEntry> entries = jarFile.entries();
492
493 while (entries.hasMoreElements()) {
494 final JarEntry entry = entries.nextElement();
495 final String entryName = entry.getName();
496 final String baseName = JNILibrary.isValidNativeLibraryName(entryName, false);
497
498 if(null != baseName) {
499 nameMap.put(baseName, entryName);
500 }
501 }
502
503 return nameMap;
504 }
505
506 /**
507 * Extract the files of the given jar file.
508 * <p>
509 * If <code>extractNativeLibraries</code> is true,
510 * native libraries are added to the given <code>nativeLibMap</code>
511 * with the base name to temp file location.<br>
512 * A file is identified as a native library,
513 * if it's name complies with the running platform's native library naming scheme.<br>
514 * Root entries are favored over non root entries in case of naming collisions.<br>
515 * Example on a Unix like machine:<br>
516 * <pre>
517 * mylib.jar!/sub1/libsour.so -> sour (mapped, unique name)
518 * mylib.jar!/sub1/libsweet.so (dropped, root entry favored)
519 * mylib.jar!/libsweet.so -> sweet (mapped, root entry favored)
520 * mylib.jar!/sweet.dll -> (dropped, not a unix library name)
521 * </pre>
522 * </p>
523 * <p>
524 * In order to be compatible with Java Web Start, we need
525 * to extract all root entries from the jar file.<br>
526 * In this case, set all flags to true <code>extractNativeLibraries </code>.
527 * <code>extractClassFiles</code>, <code>extractOtherFiles</code>.
528 * </p>
529 *
530 * @param dest
531 * @param nativeLibMap
532 * @param jarFile
533 * @param nativeLibraryPath if not null, only extracts native libraries within this path.
534 * @param extractNativeLibraries
535 * @param extractClassFiles
536 * @param extractOtherFiles
537 * @param deepDirectoryTraversal
538 * @return
539 * @throws IOException
540 */
541 public static final int extract(final File dest, final Map<String, String> nativeLibMap,
542 final JarFile jarFile,
543 final String nativeLibraryPath,
544 final boolean extractNativeLibraries,
545 final boolean extractClassFiles, final boolean extractOtherFiles) throws IOException {
546
547 if (DEBUG) {
548 System.err.println("JarUtil: extract: "+jarFile.getName()+" -> "+dest+
549 ", extractNativeLibraries "+extractNativeLibraries+" ("+nativeLibraryPath+")"+
550 ", extractClassFiles "+extractClassFiles+
551 ", extractOtherFiles "+extractOtherFiles);
552 }
553 int num = 0;
554
555 final Enumeration<JarEntry> entries = jarFile.entries();
556 while (entries.hasMoreElements()) {
557 final JarEntry entry = entries.nextElement();
558 final String entryName = entry.getName();
559
560 // Match entries with correct prefix and suffix (ignoring case)
561 final String libBaseName = JNILibrary.isValidNativeLibraryName(entryName, false);
562 final boolean isNativeLib = null != libBaseName;
563 if(isNativeLib) {
564 if(!extractNativeLibraries) {
565 if (DEBUG) {
566 System.err.println("JarUtil: JarEntry : " + entryName + " native-lib skipped, skip all native libs");
567 }
568 continue;
569 }
570 if(null != nativeLibraryPath) {
571 final String nativeLibraryPathS;
572 final String dirnameS;
573 try {
574 nativeLibraryPathS = IOUtil.slashify(nativeLibraryPath, false /* startWithSlash */, true /* endWithSlash */);
575 dirnameS = IOUtil.getDirname(entryName);
576 } catch (final URISyntaxException e) {
577 throw new IOException(e);
578 }
579 if( !nativeLibraryPathS.equals(dirnameS) ) {
580 if (DEBUG) {
581 System.err.println("JarUtil: JarEntry : " + entryName + " native-lib skipped, not in path: "+nativeLibraryPathS);
582 }
583 continue;
584 }
585 }
586 }
587
588 final boolean isClassFile = entryName.endsWith(".class");
589 if(isClassFile && !extractClassFiles) {
590 if (DEBUG) {
591 System.err.println("JarUtil: JarEntry : " + entryName + " class-file skipped");
592 }
593 continue;
594 }
595
596 if(!isNativeLib && !isClassFile && !extractOtherFiles) {
597 if (DEBUG) {
598 System.err.println("JarUtil: JarEntry : " + entryName + " other-file skipped");
599 }
600 continue;
601 }
602
603 final boolean isDir = entryName.endsWith("/");
604
605 final boolean isRootEntry = entryName.indexOf('/') == -1 &&
606 entryName.indexOf(File.separatorChar) == -1;
607
608 if (DEBUG) {
609 System.err.println("JarUtil: JarEntry : isNativeLib " + isNativeLib +
610 ", isClassFile " + isClassFile + ", isDir " + isDir +
611 ", isRootEntry " + isRootEntry );
612 }
613
614 final File destFile = new File(dest, entryName);
615 if(isDir) {
616 if (DEBUG) {
617 System.err.println("JarUtil: MKDIR: " + entryName + " -> " + destFile );
618 }
619 destFile.mkdirs();
620 } else {
621 final File destFolder = new File(destFile.getParent());
622 if(!destFolder.exists()) {
623 if (DEBUG) {
624 System.err.println("JarUtil: MKDIR (parent): " + entryName + " -> " + destFolder );
625 }
626 destFolder.mkdirs();
627 }
628 final InputStream in = new BufferedInputStream(jarFile.getInputStream(entry));
629 final OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile));
630 int numBytes = -1;
631 try {
632 numBytes = IOUtil.copyStream2Stream(BUFFER_SIZE, in, out, -1);
633 } finally {
634 in.close();
635 out.close();
636 }
637 boolean addedAsNativeLib = false;
638 if (numBytes>0) {
639 num++;
640 if (isNativeLib && ( isRootEntry || !nativeLibMap.containsKey(libBaseName) ) ) {
641 nativeLibMap.put(libBaseName, destFile.getAbsolutePath());
642 addedAsNativeLib = true;
643 fixNativeLibAttribs(destFile);
644 }
645 }
646 if (DEBUG) {
647 System.err.println("JarUtil: EXTRACT["+num+"]: [" + libBaseName + " -> ] " + entryName + " -> " + destFile + ": "+numBytes+" bytes, addedAsNativeLib: "+addedAsNativeLib);
648 }
649 }
650 }
651 return num;
652 }
653
654 /**
655 * Mitigate file permission issues of native library files, i.e.:
656 * <ul>
657 * <li>Bug 865: Safari >= 6.1 [OSX]: May employ xattr on 'com.apple.quarantine' on 'PluginProcess.app'</li>
658 * </ul>
659 */
660 private final static void fixNativeLibAttribs(final File file) {
661 // We tolerate UnsatisfiedLinkError (and derived) to solve the chicken and egg problem
662 // of loading gluegen's native library.
663 // On Safari(OSX), Bug 865, we simply hope the destination folder is executable.
665 final String fileAbsPath = file.getAbsolutePath();
666 try {
667 fixNativeLibAttribs(fileAbsPath);
668 if( DEBUG ) {
669 System.err.println("JarUtil.fixNativeLibAttribs: "+fileAbsPath+" - OK");
670 }
671 } catch (final Throwable t) {
672 if( DEBUG ) {
673 System.err.println("JarUtil.fixNativeLibAttribs: "+fileAbsPath+" - "+t.getClass().getSimpleName()+": "+t.getMessage());
674 }
675 }
676 }
677 }
678 private native static boolean fixNativeLibAttribs(String fname);
679
680 /**
681 * Validate the certificates for each native Lib in the jar file.
682 * Throws an IOException if any certificate is not valid.
683 * <pre>
684 Certificate[] rootCerts = Something.class.getProtectionDomain().
685 getCodeSource().getCertificates();
686 </pre>
687 */
688 public static final void validateCertificates(final Certificate[] rootCerts, final JarFile jarFile)
689 throws IOException, SecurityException {
690
691 if (DEBUG) {
692 System.err.println("JarUtil: validateCertificates: "+jarFile.getName());
693 }
694
695 if (rootCerts == null || rootCerts.length == 0) {
696 throw new IllegalArgumentException("Null certificates passed");
697 }
698
699 final byte[] buf = new byte[1024];
700 final Enumeration<JarEntry> entries = jarFile.entries();
701 while (entries.hasMoreElements()) {
702 final JarEntry entry = entries.nextElement();
703 if( ! entry.isDirectory() && ! entry.getName().startsWith("META-INF/") ) {
704 // only validate non META-INF and non directories
705 validateCertificate(rootCerts, jarFile, entry, buf);
706 }
707 }
708 }
709
710 /**
711 * Check the certificates with the ones in the jar file
712 * (all must match).
713 */
714 private static final void validateCertificate(final Certificate[] rootCerts,
715 final JarFile jar, final JarEntry entry, final byte[] buf) throws IOException, SecurityException {
716
717 if (DEBUG) {
718 System.err.println("JarUtil: validate JarEntry : " + entry.getName());
719 }
720
721 // API states that we must read all of the data from the entry's
722 // InputStream in order to be able to get its certificates
723
724 final InputStream is = jar.getInputStream(entry);
725 try {
726 while (is.read(buf) > 0) { }
727 } finally {
728 is.close();
729 }
730
731 // Get the certificates for the JAR entry
732 final Certificate[] nativeCerts = entry.getCertificates();
733 if (nativeCerts == null || nativeCerts.length == 0) {
734 throw new SecurityException("no certificate for " + entry.getName() + " in " + jar.getName());
735 }
736
737 if( !SecurityUtil.equals(rootCerts, nativeCerts) ) {
738 throw new SecurityException("certificates not equal for " + entry.getName() + " in " + jar.getName());
739 }
740 }
741}
static URL getClassURL(final String clazzBinName, final ClassLoader cl)
Definition IOUtil.java:408
static int copyStream2Stream(final InputStream in, final OutputStream out, final int totalNumBytes)
Copy the specified input stream to the specified output stream.
Definition IOUtil.java:190
static String getDirname(String fname)
Returns unified '/' dirname including the last '/'.
Definition IOUtil.java:433
static String slashify(final String path, final boolean startWithSlash, final boolean endWithSlash)
Definition IOUtil.java:327
Immutable RFC3986 encoded string.
Definition Uri.java:301
final int lastIndexOf(final int ch)
See String#lastIndexOf(int).
Definition Uri.java:435
final Encoded substring(final int start)
See String#substring(int).
Definition Uri.java:421
This class implements an immutable Uri as defined by RFC 2396.
Definition Uri.java:162
Uri getDirectory()
Returns this Uri's directory Uri.
Definition Uri.java:1599
final String toString()
Returns the encoded input as String, never null, same as getEncoded().
Definition Uri.java:1263
final Encoded getEncoded()
Returns the encoded input, never null.
Definition Uri.java:1255
final Encoded schemeSpecificPart
Encoded scheme-specific-part, never null.
Definition Uri.java:1194
static final String HTTP_SCHEME
{@value}
Definition Uri.java:290
final boolean isJarScheme()
Returns true, if this instance is a jar scheme, otherwise false.
Definition Uri.java:1248
static Uri cast(final String encodedUri)
Casts the given encoded String to a new Encoded instance used to create the resulting Uri instance vi...
Definition Uri.java:1073
final Uri getContainedUri()
If this instance's schemeSpecificPart contains a Uri itself, a sub-Uri, return schemeSpecificPart + #...
Definition Uri.java:1414
static Uri valueOf(final File file)
Creates a new Uri instance using the given File instance.
Definition Uri.java:1126
static final String FILE_SCHEME
{@value}
Definition Uri.java:288
static final String JAR_SCHEME
{@value}
Definition Uri.java:294
static final String HTTPS_SCHEME
{@value}
Definition Uri.java:292
static final char JAR_SCHEME_SEPARATOR
A JAR sub-protocol is separated from the JAR entry w/ this separator {@value}.
Definition Uri.java:296
static final char SCHEME_SEPARATOR
{@value}
Definition Uri.java:282
static Uri getJarFileUri(final String clazzBinName, final ClassLoader cl)
The Class's "com.jogamp.common.GlueGenVersion" Uri jar:sub_protocol:/some/path/gluegen-rt....
Definition JarUtil.java:304
static Uri getJarFileUri(final Uri baseUri, final Uri.Encoded jarFileName)
Definition JarUtil.java:323
static Uri getJarUri(final String clazzBinName, final ClassLoader cl)
The Class's "com.jogamp.common.GlueGenVersion" Uri jar:sub_protocol:/some/path/gluegen-rt....
Definition JarUtil.java:140
static JarFile getJarFile(final Uri jarFileUri)
Definition JarUtil.java:390
static Uri.Encoded getJarBasename(final String clazzBinName, final ClassLoader cl)
The Class's com.jogamp.common.GlueGenVersion Uri jar:sub_protocol:/some/path/gluegen-rt....
Definition JarUtil.java:249
static boolean hasJarUri(final String clazzBinName, final ClassLoader cl)
Returns true if the Class's "com.jogamp.common.GlueGenVersion" is loaded from a JarFile and hence has...
Definition JarUtil.java:117
static Uri getJarFileUri(final Uri jarSubUri)
Definition JarUtil.java:336
static void setResolver(final Resolver r)
Setting a custom Resolver instance.
Definition JarUtil.java:87
static JarFile getJarFile(final String clazzBinName, final ClassLoader cl)
Definition JarUtil.java:379
static Uri getRelativeOf(final Class<?> classFromJavaJar, final Uri.Encoded cutOffInclSubDir, final Uri.Encoded relResPath)
Locates the Jar file Uri of a given resource relative to a given class's Jar's Uri.
Definition JarUtil.java:448
static final int extract(final File dest, final Map< String, String > nativeLibMap, final JarFile jarFile, final String nativeLibraryPath, final boolean extractNativeLibraries, final boolean extractClassFiles, final boolean extractOtherFiles)
Extract the files of the given jar file.
Definition JarUtil.java:541
static final void validateCertificates(final Certificate[] rootCerts, final JarFile jarFile)
Validate the certificates for each native Lib in the jar file.
Definition JarUtil.java:688
static Uri.Encoded getJarEntry(final Uri classJarUri)
The Class's Jar Uri jar:sub_protocol:/some/path/gluegen-rt.jar!
Definition JarUtil.java:262
static Uri.Encoded getJarBasename(final Uri classJarUri)
The Class's Jar Uri jar:sub_protocol:/some/path/gluegen-rt.jar!
Definition JarUtil.java:190
static Map< String, String > getNativeLibNames(final JarFile jarFile)
Return a map from native-lib-base-name to entry-name.
Definition JarUtil.java:485
static Uri getJarFileUri(final Uri.Encoded jarSubUriS)
Definition JarUtil.java:349
static Uri getJarEntryUri(final Uri jarFileUri, final Uri.Encoded jarEntry)
Definition JarUtil.java:363
static final SecurityManager getSecurityManager()
Call wrapper for System#getSecurityManager().
Helper routines for logging and debugging.
Definition Debug.java:35
static final boolean debug(final String subcomponent)
Definition Debug.java:63
Static JNI Native Libraries handler.
static final String isValidNativeLibraryName(final String libName, final boolean isLowerCaseAlready)
Comparison of prefix and suffix of the given libName's basename is performed case insensitive
Platform Properties derived from Java properties.
Exposing types describing the underlying platform.
Interface allowing users to provide an URL resolver that will convert custom classloader URLs like Ec...
Definition JarUtil.java:71