Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package javajs.async

File Assets.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

100
184
39
2
594
342
111
0.6
4.72
19.5
2.85

Classes

Class Line # Actions
Assets 70 175 105
0.00%
Assets.Asset 112 9 6
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    package javajs.async;
2   
3    import java.io.File;
4    import java.io.IOException;
5    import java.io.InputStream;
6    import java.io.OutputStream;
7    import java.net.MalformedURLException;
8    import java.net.URI;
9    import java.net.URISyntaxException;
10    import java.net.URL;
11    import java.util.Arrays;
12    import java.util.HashMap;
13    import java.util.HashSet;
14    import java.util.Map;
15    import java.util.zip.ZipEntry;
16    import java.util.zip.ZipInputStream;
17   
18    import swingjs.api.JSUtilI;
19   
20    /**
21    * The Assets class allows assets such as images and property files to be
22    * combined into zip files rather than delivered individually. The Assets
23    * instance is a singleton served by a set of static methods. In particular, the
24    * three add(...) methods are used to create asset references, which include an
25    * arbitrary name, a path to a zip file asset, and one or more class paths that
26    * are covered by this zip file asset.
27    *
28    * For example:
29    *
30    * <code>
31    static {
32    try {
33    Assets.add(new Assets.Asset("osp", "osp-assets.zip", "org/opensourcephysics/resources"));
34    Assets.add(new Assets.Asset("tracker", "tracker-assets.zip",
35    "org/opensourcephysics/cabrillo/tracker/resources"));
36    Assets.add(new Assets.Asset("physlets", "physlet-assets.zip", new String[] { "opticsimages", "images" }));
37    // add the Info.assets last so that it can override these defaults
38    if (OSPRuntime.isJS) {
39    Assets.add(OSPRuntime.jsutil.getAppletInfo("assets"));
40    }
41    } catch (Exception e) {
42    OSPLog.warning("Error reading assets path. ");
43    }
44   
45    }
46    * </code>
47    *
48    * It is not clear that Java is well-served by this zip-file loading, but
49    * certainly JavaScript is. What could be 100 downloads is just one, and SwingJS
50    * (but not Java) can cache individual ZipEntry instances in order to unzip them
51    * independently only when needed. This is potentially a huge savings.
52    *
53    * Several static methods can be used to retrieve assets. Principal among those
54    * are:
55    *
56    * <code>
57    * getAssetBytes(String fullPath)
58    * getAssetString(String fullPath)
59    * getAssetStream(String fullPath)
60    * </code>
61    *
62    * If an asset is not found in a zip file, then it will be loaded from its fullPath.
63    *
64    *
65    *
66    * @author hansonr
67    *
68    */
69   
 
70    public class Assets {
71   
72    public static boolean isJS = /** @j2sNative true || */
73    false;
74   
75    public static JSUtilI jsutil;
76   
 
77  0 toggle static {
78  0 try {
79  0 if (isJS) {
80  0 jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
81    }
82   
83    } catch (Exception e) {
84  0 System.err.println("Assets could not create swinjs.JSUtil instance");
85    }
86    }
87   
88    private Map<String, Map<String, ZipEntry>> htZipContents = new HashMap<>();
89   
90    private static boolean doCacheZipContents = true;
91   
92    private static Assets instance = new Assets();
93   
 
94  0 toggle private Assets() {
95    }
96   
97    private Map<String, Asset> assetsByPath = new HashMap<>();
98   
99    private String[] sortedList = new String[0];
100   
101    /**
102    * If this object has been cached by SwingJS, add its bytes to the URL, URI, or
103    * File
104    *
105    * @param URLorURIorFile
106    * @return
107    */
 
108  0 toggle public static byte[] addJSCachedBytes(Object URLorURIorFile) {
109  0 return (isJS ? jsutil.addJSCachedBytes(URLorURIorFile) : null);
110    }
111   
 
112    public static class Asset {
113    String name;
114    URI uri;
115    String classPath;
116    String zipPath;
117    String[] classPaths;
118   
 
119  0 toggle public Asset(String name, String zipPath, String[] classPaths) {
120  0 this.name = name;
121  0 this.zipPath = zipPath;
122  0 this.classPaths = classPaths;
123    }
124   
 
125  0 toggle public Asset(String name, String zipPath, String classPath) {
126  0 this.name = name;
127  0 this.zipPath = zipPath;
128  0 uri = getAbsoluteURI(zipPath); // no spaces expected here.
129  0 this.classPath = classPath.endsWith("/") ? classPath : classPath + "/";
130    }
131   
 
132  0 toggle public URL getURL(String fullPath) throws MalformedURLException {
133  0 return (fullPath.indexOf(classPath) < 0 ? null
134    : new URL("jar", null, uri + "!/" + fullPath));//.replaceAll(" ", "%20")));
135    }
136   
 
137  0 toggle @Override
138    public String toString() {
139  0 return "{" + "\"name\":" + "\"" + name + "\"," + "\"zipPath\":" + "\"" + zipPath + "\"," + "\"classPath\":"
140    + "\"" + classPath + "\"" + "}";
141    }
142   
143    }
144   
 
145  0 toggle public static Assets getInstance() {
146  0 return instance;
147    }
148   
149    /**
150    * The difference here is that URL will not insert the %20 for space that URI will.
151    *
152    * @param path
153    * @return
154    */
 
155  0 toggle @SuppressWarnings("deprecation")
156    public static URL getAbsoluteURL(String path) {
157  0 URL url = null;
158  0 try {
159  0 url = (path.indexOf(":/") < 0 ? new File(new File(path).getAbsolutePath()).toURL() : new URL(path));
160  0 if (path.indexOf("!/")>=0)
161  0 url = new URL("jar", null, url.toString());
162    } catch (MalformedURLException e) {
163  0 e.printStackTrace();
164    }
165  0 return url;
166    }
167   
 
168  0 toggle public static URI getAbsoluteURI(String path) {
169  0 URI uri = null;
170  0 try {
171  0 uri = (path.indexOf(":/") < 0 ? new File(new File(path).getAbsolutePath()).toURI() : new URI(path));
172    } catch (URISyntaxException e) {
173  0 e.printStackTrace();
174    }
175  0 return uri;
176    }
177   
178    /**
179    * Allows passing a Java Asset or array of Assets or a JavaScript Object or
180    * Object array that contains name, zipPath, and classPath keys; in JavaScript,
181    * the keys can have multiple .
182    *
183    * @param o
184    */
 
185  0 toggle public static void add(Object o) {
186  0 if (o == null)
187  0 return;
188  0 try {
189  0 if (o instanceof Object[]) {
190  0 Object[] a = (Object[]) o;
191  0 for (int i = 0; i < a.length; i++)
192  0 add(a[i]);
193  0 return;
194    }
195    // In JavaScript this may not actually be an Asset, only a proxy for that.
196    // Just testing for keys. Only one of classPath and classPaths is allowed.
197  0 Asset a = (Asset) o;
198  0 if (a.name == null || a.zipPath == null || a.classPath == null && a.classPaths == null
199    || a.classPath != null && a.classPaths != null) {
200  0 throw new NullPointerException("Assets could not parse " + o);
201    }
202  0 if (a.classPaths == null) {
203    // not possible in Java, but JavaScript may be passing an array of class paths
204  0 add(a.name, a.zipPath, a.classPath);
205    } else {
206  0 add(a.name, a.zipPath, a.classPaths);
207    }
208    } catch (Throwable t) {
209  0 throw new IllegalArgumentException(t.getMessage());
210    }
211    }
212   
 
213  0 toggle public static void add(String name, String zipFile, String path) {
214  0 add(name, zipFile, new String[] { path });
215    }
216   
217    private static HashSet<String> loadedAssets = new HashSet<>();
218   
 
219  0 toggle public static boolean hasLoaded(String name) {
220  0 return loadedAssets.contains(name);
221    }
222   
 
223  0 toggle public static void reset() {
224  0 getInstance().htZipContents.clear();
225  0 getInstance().assetsByPath.clear();
226  0 getInstance().sortedList = new String[0];
227    }
228   
 
229  0 toggle public static void add(String name, String zipFile, String[] paths) {
230  0 getInstance()._add(name, zipFile, paths);
231    }
232   
 
233  0 toggle private void _add(String name, String zipFile, String[] paths) {
234  0 if (hasLoaded(name)) {
235  0 System.err.println("Assets warning: Asset " + name + " already exists");
236    }
237  0 loadedAssets.add(name);
238  0 for (int i = paths.length; --i >= 0;) {
239  0 assetsByPath.put(paths[i], new Asset(name, zipFile, paths[i]));
240    }
241  0 resort();
242    }
243   
244   
245    /**
246    * Gets the asset, preferably from a zip file asset, but not necessarily.
247    *
248    * @param assetPath
249    * @return
250    */
 
251  0 toggle public static byte[] getAssetBytes(String assetPath) {
252  0 return getAssetBytes(assetPath, false);
253    }
254   
255    /**
256    * Gets the asset, preferably from a zip file asset, but not necessarily.
257    *
258    * @param assetPath
259    * @return
260    */
 
261  0 toggle public static String getAssetString(String assetPath) {
262  0 return getAssetString(assetPath, false);
263    }
264   
265    /**
266    * Gets the asset, preferably from a zip file asset, but not necessarily.
267    *
268    * @param assetPath
269    * @return
270    */
 
271  0 toggle public static InputStream getAssetStream(String assetPath) {
272  0 return getAssetStream(assetPath, false);
273    }
274   
275    /**
276    * Gets the asset from a zip file.
277    *
278    * @param assetPath
279    * @return
280    */
 
281  0 toggle public static byte[] getAssetBytesFromZip(String assetPath) {
282  0 return getAssetBytes(assetPath, true);
283    }
284   
285    /**
286    * Gets the asset from a zip file.
287    *
288    * @param assetPath
289    * @return
290    */
 
291  0 toggle public static String getAssetStringFromZip(String assetPath) {
292  0 return getAssetString(assetPath, true);
293    }
294   
295    /**
296    * Gets the asset from a zip file.
297    *
298    * @param assetPath
299    * @return
300    */
 
301  0 toggle public static InputStream getAssetStreamFromZip(String assetPath) {
302  0 return getAssetStream(assetPath, true);
303    }
304   
305   
306    /**
307    * Get the contents of a path from a zip file asset as byte[], optionally loading
308    * the resource directly using a class loader.
309    *
310    * @param path
311    * @param zipOnly
312    * @return
313    */
 
314  0 toggle private static byte[] getAssetBytes(String path, boolean zipOnly) {
315  0 byte[] bytes = null;
316  0 try {
317  0 URL url = getInstance()._getURLFromPath(path, true);
318  0 if (url == null && !zipOnly) {
319  0 url = getAbsoluteURL(path);
320    //url = Assets.class.getResource(path);
321    }
322  0 if (url == null)
323  0 return null;
324  0 if (isJS) {
325  0 bytes = jsutil.getURLBytes(url);
326  0 if (bytes == null) {
327  0 url.openStream();
328  0 bytes = jsutil.getURLBytes(url);
329    }
330    } else {
331  0 bytes = getLimitedStreamBytes(url.openStream(), -1, null);
332    }
333    } catch (Throwable t) {
334  0 t.printStackTrace();
335    }
336  0 return bytes;
337    }
338   
339    /**
340    * Get the contents of a path from a zip file asset as a String, optionally
341    * loading the resource directly using a class loader.
342    *
343    * @param path
344    * @param zipOnly
345    * @return
346    */
 
347  0 toggle private static String getAssetString(String path, boolean zipOnly) {
348  0 byte[] bytes = getAssetBytes(path, zipOnly);
349  0 return (bytes == null ? null : new String(bytes));
350    }
351   
352    /**
353    * Get the contents of a path from a zip file asset as an InputStream, optionally
354    * loading the resource directly using a class loader.
355    *
356    * @param path
357    * @param zipOnly
358    * @return
359    */
 
360  0 toggle private static InputStream getAssetStream(String path, boolean zipOnly) {
361  0 try {
362  0 URL url = getInstance()._getURLFromPath(path, true);
363  0 if (url == null && !zipOnly) {
364  0 url = Assets.class.getClassLoader().getResource(path);
365    }
366  0 if (url != null)
367  0 return url.openStream();
368    } catch (Throwable t) {
369    }
370  0 return null;
371    }
372    /**
373    * Determine the path to an asset. If not found in a zip file asset, return the
374    * absolute path to this resource.
375    *
376    * @param fullPath
377    * @return
378    */
 
379  0 toggle public static URL getURLFromPath(String fullPath) {
380  0 return getInstance()._getURLFromPath(fullPath, false);
381    }
382   
383    /**
384    * Determine the path to an asset. If not found in a zip file asset, optionally
385    * return null or the absolute path to this resource.
386    *
387    * @param fullPath
388    * @param zipOnly
389    * @return the URL to this asset, or null if not found.
390    */
 
391  0 toggle public static URL getURLFromPath(String fullPath, boolean zipOnly) {
392  0 return getInstance()._getURLFromPath(fullPath, zipOnly);
393    }
394   
 
395  0 toggle private URL _getURLFromPath(String fullPath, boolean zipOnly) {
396  0 URL url = null;
397  0 try {
398  0 if (fullPath.startsWith("/"))
399  0 fullPath = fullPath.substring(1);
400  0 for (int i = sortedList.length; --i >= 0;) {
401  0 if (fullPath.startsWith(sortedList[i])) {
402  0 url = assetsByPath.get(sortedList[i]).getURL(fullPath);
403  0 ZipEntry ze = findZipEntry(url);
404  0 if (ze == null)
405  0 break;
406  0 if (isJS) {
407  0 jsutil.setURLBytes(url, jsutil.getZipBytes(ze));
408    }
409  0 return url;
410    }
411    }
412  0 if (!zipOnly)
413  0 return getAbsoluteURL(fullPath);
414    } catch (MalformedURLException e) {
415    }
416  0 return null;
417    }
418   
 
419  0 toggle public static ZipEntry findZipEntry(URL url) {
420  0 String[] parts = getJarURLParts(url.toString());
421  0 if (parts == null || parts[0] == null || parts[1].length() == 0)
422  0 return null;
423  0 return findZipEntry(parts[0], parts[1]);
424    }
425   
 
426  0 toggle public static ZipEntry findZipEntry(String zipFile, String fileName) {
427  0 return getZipContents(zipFile).get(fileName);
428    }
429   
430    /**
431    * Gets the contents of a zip file.
432    *
433    * @param zipPath the path to the zip file
434    * @return a set of file names in alphabetical order
435    */
 
436  0 toggle public static Map<String, ZipEntry> getZipContents(String zipPath) {
437  0 return getInstance()._getZipContents(zipPath);
438    }
439   
 
440  0 toggle private Map<String, ZipEntry> _getZipContents(String zipPath) {
441  0 URL url = getURLWithCachedBytes(zipPath); // BH carry over bytes if we have them already
442  0 Map<String, ZipEntry> fileNames = htZipContents.get(url.toString());
443  0 if (fileNames != null)
444  0 return fileNames;
445  0 try {
446    // Scan URL zip stream for files.
447  0 return readZipContents(url.openStream(), url);
448    } catch (Exception ex) {
449  0 ex.printStackTrace();
450  0 return null;
451    }
452    }
453   
454    /**
455    * Deconstruct a jar URL into two parts, before and after "!/".
456    *
457    * @param source
458    * @return
459    */
 
460  0 toggle public static String[] getJarURLParts(String source) {
461  0 int n = source.indexOf("!/");
462  0 if (n < 0)
463  0 return null;
464  0 String jarfile = source.substring(0, n).replace("jar:", "");
465  0 while (jarfile.startsWith("//"))
466  0 jarfile = jarfile.substring(1);
467  0 return new String[] { jarfile, (n == source.length() - 2 ? null : source.substring(n + 2)) };
468    }
469   
470    /**
471    * Get the contents of any URL as a byte array. This method does not do any asset check. It just gets the url data as a byte array.
472    *
473    * @param url
474    * @return byte[]
475    *
476    * @author hansonr
477    */
 
478  0 toggle public static byte[] getURLContents(URL url) {
479  0 if (url == null)
480  0 return null;
481  0 try {
482  0 if (isJS) {
483    // Java 9! return new String(url.openStream().readAllBytes());
484  0 return jsutil.readAllBytes(url.openStream());
485    }
486  0 return getLimitedStreamBytes(url.openStream(), -1, null);
487    } catch (IOException e) {
488  0 e.printStackTrace();
489    }
490  0 return null;
491    }
492   
493    /**
494    *
495    * Convert a file path to a URL, retrieving any cached file data, as from DnD.
496    * Do not do any actual data transfer. This is a swingjs.JSUtil service.
497    *
498    * @param path
499    * @return
500    */
 
501  0 toggle private static URL getURLWithCachedBytes(String path) {
502  0 URL url = getAbsoluteURL(path);
503  0 if (url != null)
504  0 addJSCachedBytes(url);
505  0 return url;
506    }
507   
 
508  0 toggle private Map<String, ZipEntry> readZipContents(InputStream is, URL url) throws IOException {
509  0 HashMap<String, ZipEntry> fileNames = new HashMap<String, ZipEntry>();
510  0 if (doCacheZipContents)
511  0 htZipContents.put(url.toString(), fileNames);
512  0 ZipInputStream input = new ZipInputStream(is);
513  0 ZipEntry zipEntry = null;
514  0 int n = 0;
515  0 while ((zipEntry = input.getNextEntry()) != null) {
516  0 if (zipEntry.isDirectory() || zipEntry.getSize() == 0)
517  0 continue;
518  0 n++;
519  0 String fileName = zipEntry.getName();
520  0 fileNames.put(fileName, zipEntry); // Java has no use for the ZipEntry, but JavaScript can read it.
521    }
522  0 input.close();
523  0 System.out.println("Assets: " + n + " zip entries found in " + url); //$NON-NLS-1$
524  0 return fileNames;
525    }
526   
 
527  0 toggle private void resort() {
528  0 sortedList = new String[assetsByPath.size()];
529  0 int i = 0;
530  0 for (String path : assetsByPath.keySet()) {
531  0 sortedList[i++] = path;
532    }
533  0 Arrays.sort(sortedList);
534    }
535   
536   
537    /**
538    * Only needed for Java
539    *
540    * @param is
541    * @param n
542    * @param out
543    * @return
544    * @throws IOException
545    */
 
546  0 toggle private static byte[] getLimitedStreamBytes(InputStream is, long n, OutputStream out) throws IOException {
547   
548    // Note: You cannot use InputStream.available() to reliably read
549    // zip data from the web.
550   
551  0 boolean toOut = (out != null);
552  0 int buflen = (n > 0 && n < 1024 ? (int) n : 1024);
553  0 byte[] buf = new byte[buflen];
554  0 byte[] bytes = (out == null ? new byte[n < 0 ? 4096 : (int) n] : null);
555  0 int len = 0;
556  0 int totalLen = 0;
557  0 if (n < 0)
558  0 n = Integer.MAX_VALUE;
559  0 while (totalLen < n && (len = is.read(buf, 0, buflen)) > 0) {
560  0 totalLen += len;
561  0 if (toOut) {
562  0 out.write(buf, 0, len);
563    } else {
564  0 if (totalLen > bytes.length)
565  0 bytes = Arrays.copyOf(bytes, totalLen * 2);
566  0 System.arraycopy(buf, 0, bytes, totalLen - len, len);
567  0 if (n != Integer.MAX_VALUE && totalLen + buflen > bytes.length)
568  0 buflen = bytes.length - totalLen;
569    }
570    }
571  0 if (toOut)
572  0 return null;
573  0 if (totalLen == bytes.length)
574  0 return bytes;
575  0 buf = new byte[totalLen];
576  0 System.arraycopy(bytes, 0, buf, 0, totalLen);
577  0 return buf;
578    }
579   
580    /**
581    * Return all assets in the form that is appropriate for the Info.assets value in SwingJS.
582    *
583    */
 
584  0 toggle @Override
585    public String toString() {
586  0 String s = "[";
587  0 for (int i = 0; i < sortedList.length; i++) {
588  0 Asset a = assetsByPath.get(sortedList[i]);
589  0 s += (i == 0 ? "" : ",") + a;
590    }
591  0 return s + "]";
592    }
593   
594    }