java2script/SwingJS Notes ========================= updated 12/31/2020 -- full support for 64-bit long updated 12/6/2020 -- note about restrictions on long, including BitSet and Scanner updated 3/21/2020 -- adds note about HashMap, Hashtable, and HashSet iterator ordering updated 3/20/2020 -- adds note about interning, new String("xxx"), and "xxx" updated 2/26/2020 -- adds Graphics.setClip issue updated 12/22/19 -- additional issues updated 11/03/19 -- adds information about File.exists() and points to src/javajs/async updated 10/26/19 -- adds information about File.createTempFile() updated 8/16/19 -- minor typos and added summary paragraph updated 7/19/19 -- clarification that AWT and Swing classes are supported directly updated 5/13/19 -- Mandarin U+79D8 reserved character; Missing Math methods; int and long updated 5/10/19 -- adds a section on static issues in multi-(duplicate)-applet pages updated 1/4/19 -- nio updated 9/15/18 -- adds integer 1/0 == Infinity updated 7/24/18 -- most classes replaced with https://github.com/frohoff/jdk8u-jdk updated 6/5/17 -- reserved package name "window" updated 3/11/17 -- myClass.getField updated 3/7/17 -- overloading of JSplitPane.setDividerLocation updated 3/2/17 -- more indication of classes not implemented (KeyListener) ---IMPORTANT CHARACTER SET NOTE--- It is critical that all development work in Java2Script be done in UTF-8. This means: - making sure your Eclipse project is set up for UTF-8 (not the Eclipse default?) - making sure your server can serve up UTF-8 by default for any browser-loaded files - making sure you don't edit a Java2Script class file or one of the site .js files using a non-UTF-8 editor. It may replace non-Latin characters with "?" or garbage. - making sure that your web pages are delivered with proper headings indicating HTML5 and UTF-8
Note that the DOCTYPE tag is critical for some browsers to switch into HTML5 mode. (MSIE?) In particular, the Mandarin character 秘 (mi; "secret") is used extensively throughout the SwingJS class files to distinguish j2s-specific fields and methods that must not ever be shadowed or overridden by subclasses. For example, we see in java.lang.Thread.java: public static JSThread 秘thisThread; ---------------------------------- ============================================================================= SwingJS and OpenJDK 8+ ============================================================================= SwingJS implements a wide range of the Java language in JavaScript. The base version for this implementation is OpenJDK8. some classes are implemented using older source code, and there are some missing methods. For the most part, this is no real problem. You can add or modify any java class just be adding it as source in your project. Or (preferably) you can contact me, and I can get it into the distribution. Or (even more preferably) you can do that via a patch submission. ================= DESIGN PHILOSOPHY ================= The java2script/SwingJS design goal is to recreate a recognizable, easily debuggable equivalent in JavaScript for as much of Java as practical. This means, for example, that one can call in JavaScript new java.util.Hashtable() and for all practical purposes it will appear that Java is running. The goal of java2script/SwingJS is NOT to reproduce Java byte code processing in a browser. We leave that task and its own issues to others. Here, instead, we have a working JavaScript version of the Java classes along with runtime assistance in the j2sClazz.js library. This design has several advantages: 1) It leads to much smaller downloads, since the class loader can dynamically load code at the class level. 2) It allow the browser to use its own optimizations and features, not to ignore those. This leads to huge performance gains and in many cases much simpler coding. 3) It allows for in-browser debugging and analysis. 4) It allows for code switching between Java and JavaScript. Working Java code can be annotated (@j2sNative, @j2sAlias, @j2sIgnore) in a fashion that allows the code to run slightly differently in a Java than a JavaScript environment. For example: int delayMS = /** @j2sNative 10 ||*/2; will read "var delayMS = 10 || 2;" (i.e. 10) in JavaScript but read by the Java compiler as "int delayMS = 2". 5) Just generally, it allows for a much more integrated environment. JavaScript on the page can call into any SwingJS program, and, likewise, any SwingJS code can access anything on the page. Method and Field Disambiguation ------------------------------- This is no problem. SwingJS has no problem with the overloading of methods, for example: public void print(int b); public void print(float b); JavaScript does not allow overloading of methods, and the common practice in Java of naming a field the same as a method -- isAllowed and isAllowed() -- is not possible in JavaScript. As a result, SwingJS implements "fully-qualified" method names using "$" parameter type separation. Thus, these methods in SwingJS will be referred to as print$I and print$F. The rules for this encoding are relatively simple: 1. The seven primitive types in Java are encoded $I (int), $L (long), $F (float), $D (double), $B (byte) $Z (boolean), and $H (short). 2. String and Object are encoded as $S and $O, respectively. 3. "java_lang_" is dropped for all other classes in the java.lang package (as in Java). For example: $StringBuffer, not $java_lang_StringBuffer 4. All other classes are encoded as "$" + Class.getName().replace(".","_") For example, in Java we see: public void equals(Object o) {...} Whereas in SwingJS we have: Clazz.newMeth(C$, 'equals$O', function (o) {...} And this.getContentPane().add(bar, "North"); becomes this.getContentPane$().add$java_awt_Component$O(bar, "North"); 5. Arrays are indicated with appended "A" for each level. So setDataVector(Object[][] dataVector, Object[] columnIdentifiers) becomes setDataVector$OAA$OA(dataVector, columnIdentifiers) (It is recognized that this design does introduce a bit of ambiguity, in that in principal there could be user class named XA and X in the same package, and methods a(X[]) and a(XA) in the same class that cannot be distinguished. The benefit of this simple system, however, triumphed over the unlikelyhood of that scenario.) The transpiler could be set to flag this possibility. 6. Constructors are prepended with "c$". So public JLabel(String text) {...} becomes: Clazz.newMeth(C$, 'c$$S', function (text) {...}); Field disambiguation involves prepending. In Java, a class and its subclass can both have the same field name, such as boolean visible; When this happens, it is called "shadowing", and though not recommended, Java allows it. The Java2Script transpiler will prepend such shadowing fields with "$" so that the subclass instance has both "visible" (for use in its methods inherited from its superclass) and "$visible" (for its own methods). Thus, we might see in Java: this.visible = super.visible; while in SwingJS we will see: this.$visible=this.visible; since JavaScript does not have the "super" keyword. Parameterless methods such as toString() are appended with "$" to become toString$(). The one exception to this rule is private methods, which are saved in (truly) private array in the class (and are not accessible by reflection). Private parameterless methods retain their simple Java name, since they cannot conflict with field names. This renaming of methods has a few consequences, which are discussed more fully below. See particularly the section on "qualified field and method names", where it is described how you can use packages or classes or interfaces with ".api.js" in them to represent JavaScript objects for which all method names are to be left unqualified, and how individual methods can have more than one name using @j2sAlias. The swingjs.api.js package in particular contains a number of useful interfaces that you can import into your project for JavaScript-specific capabilities. Applet vs. Application ---------------------- One of the very cool aspects of SwingJS is that it doesn't particularly matter if a browser-based Java app is an "applet" or an "application". We don't need JNLP (Java Network Launch Protocol) because now we can just start up any Java application in a browser just as easily as any applet. The associative array that passes information to the SwingJS applet (information that formerly might have been part of the APPLET tag, such as width, height, and codebase, always referred to in our writing as "the Info array") allows the option to specify the JApplet/Applet "code" class or the application "main" class. Either one will run just fine. Performance ----------- Obviously, there are limitations. One is performance, but we have seen reproducible performance at 1/6 - 1/3 the speed of Java. Achieving this performance may require some refactoring of the Java to make it more efficient in both Java and JavaScript. "for" loops need to be more carefully crafted; use of "new" and "instanceof" need to be minimized in critical areas. Note that method overloading -- that is, the same method name with different parameters, such as read(int) and read(byte) -- is no longer any problem. Threads ------- Although there is only a single thread in JavaScript, meaning Thread.wait(), Thread.sleep(int) and Thread.notify() cannot be reproduced, we have found that this is not a serious limitation. For example, javax.swing.Timer() works perfectly in JavaScript. All it means is that threads that use sleep(int) or notify() must be refactored to allow Timer-like callbacks. That is, they must allow full exit and re-entry of Thread.run(), not the typical while/sleep motif. The key is to create a state-based run() that can be exited and re-entered in JavaScript. The javajs.async package can be added to any Java program to provide Java+JavaScript asynchronous classes, including AsyncColorChooser, AsyncDialog, AsyncFileChooser, and AsyncSwingWorker. All of these classes work just as well in Java as in JavaScript. There is no need to run them only when in JavaScript. Static fields ------------- Final static primitive "constant" fields (String, boolean, int, etc.) such as static final int TEST = 3; static final String MY_STRING = "my " + "string"; are converted to their primitive form automatically by the Eclipse Java compiler and do not appear in the JavaScript by their names. Other static fields are properties of their class and can be used as expected. Note, however, that SwingJS runs all "Java" code on a page in a common "jvm" (like older versions of Java). So, like the older Java schema, the JavaScript equivalents of both applets and applications will share all of their static fields and methods. This includes java.lang.System. Basically, SwingJS implementations of Java run in a browser page-based sandbox instead of an applet-specific one. In general, this is no problem. But if we are to implement pages with multiple applets present, we must be sure to only have static references that are "final" or specifically meant to be shared in a JavaScript environment only (since they will not be shared in Java). A simple solution, if static non-constant references are needed, is to attach the field to Thread.currentThread.threadGroup(), which is an applet-specific reference. Be sure, if you do this, that you use explicit setters and getters: For example, private static String myvar; ... public void setMyVar(String x) { ThreadGroup g = Thread.currentThread().threadGroup(); /** * @j2sNative g._myvar = x; * */ { myvar = x; } } public String getMyVar() { ThreadGroup g = Thread.currentThread().threadGroup(); /** * @j2sNative return g._myvar || null; * */ { return myvar; } } in Java will get and set x the same in JavaScript and in Java. A convenient way to do this in general is to supply a singleton class with explicitly private-only constructors and then refer to it in Java and in JavaScript instead of using static field, referring to myclass.getIntance().xxx instead of myclass.xxx in Java (and JavaScript). This was done extensively in the Jalview project. See jalview.bin.Instance. Helper Packages -- swingjs/ and javajs/ --------------------------------------- The SwingJS library is the swingjs/ package. There are interfaces that may be of assistance in swingjs/api, but other than that, it is not recommended that developers access classes in this package. The "public" nature of their methods is really an internal necessity. Most access to this package in working Java should be via the swingjs.api.JSUtilI interface. In addition to swingjs/, though, there are several useful classes in the javajs/ package that could be very useful. This package is a stand-alone package that can be cloned in any Java project that also would be great to have in any JavaScript project -- SwingJS-related or not. Functionality ranges from reading and writing various file formats, including PDF, BMP, PNG, GIF, JPG, JSON, ZIP, and CompoundDocument formats. A variety of highly efficient three- and four-dimensional point, vector, matrix, and quaternion classes are included, as they were developed for JSmol and inherited from that project. Of particular interest should be javajs/async/, which includes javajs.async.Async javajs.async.AsyncColorChooser javajs.async.AsyncDialog javajs.async.AsyncFileChooser See javajs.async.Async JavaDoc comments for a full description of these useful classes. Modal Dialogs ------------- Although true modal dialogs are not possible with only one thread, a functional equivalent -- asynchronous modal dialogs -- is relatively easy to set up. All the JOptionPane dialogs will return PropertyChangeEvents to signal that they have been disposed of and containing the results. See below and classes in the javajs.async package. Native calls ------------ Native calls in Java are calls to operating system methods that are not in Java. JavaScript has no access to these, of course, and they must all be replaced by JavaScript equivalents. Fortunately, they are not common, and those that are present in Java (for example, in calculating checksums in ZIP file creation) are at a low enough level that most developers do not utilize them or do not even have access to them. All native calls in Java classes have been replaced by Java or JavaScript equivalents. Swing GUI Peers and UIClasses ----------------------------- One of the biggest adaptations introduced in SwingJS is in the area of the graphical user interface. Basically, what we have is a Java Swing "LookAndFeel" customized for HTML. The issue here is complex but workable. In Java there are two background concepts -- the Component "peer" (one per "heavy-weight" component, such as a Frame) and the component "uiClass" (one per component, such as BasicButtonUI or BasicTextFieldUI). Peers are native objects of the operating system. These are the virtual buttons and text areas that the user is interacting with at a very base level. They are chunks of low-level code that paint the screen to give the illusion that you really are pressing a button or typing text ot the screen. Their events are being passed on to Java or the browser by the operating system. UI classes provide a consistent "look and feel" for these native objects, rendering them onto the native window canvas and handling all user-generated events. They paint the borders, the backgrounds, the highlights, of every control you see in Java. There is one-to-one correspondence of Swing classes and UI classes. Setting the Look and Feel for a project amounts to selecting the directory from which to draw these UI classes. Java's UI class interfaces can be found in the javax.swing.plaf ("platform look and feel") package. Individual look and feel implementations are found in sun.plaf.basic, sun.plaf.metal, and other such specialized packages. Early on in the development of SwingJS, we decided not to fully reproduce the painfully detailed bit-by-bit painting of controls as is done in Java. Instead, we felt it was wiser to utilize the standard HTML5 UI capabilities as much as possible, using DIV, and INPUT especially, with extensive use of CSS and sometimes jQuery (menus, and sliders, for example). Thus, we have created a new set of UIs -- the "HTML5 Look and Feel". These classes can be found in swingjs.plaf. Besides being more adaptable, this approach allows far more versatility to SwingJS developers, allowing them to modify the GUI to suit their needs if desired. In SwingJS, since we have no access to native peers except through the browser DOM, it seemed logical to merge the peer and UI idea. So instead of having one peer per heavy-weight control and one UI class instance for each control type, we just have one UI class instance per control, and that UI class instance is what is being referred to when a "peer" is notified. In some ways this is a throw back to when all of Swing's components were subclasses of specific AWT components such as Button and List. These "heavy-weight components" all had their own individual native peers and thus automatically took on the look and feel provided by the OS. Later Swing versions implemented full look and feel for all peers, leaving only JDialog, JFrame, and a few other classes to have native peers. But in SwingJS we have again a 1:1 map of component and UI class/peer instance. The origin of most issues (read "bugs") in relation to the GUI will probably be found in the swingjs.plaf JSxxxxUI.java code. Swing-only Components -- no longer an issue ------------------------------------------- Swing was introduced into Java well after the Java Abstract Window Toolkit (AWT) was well established. As such, its designers chose to allow AWT controls such as Button and List to be used alongside their Swing counterparts JButton and JList. Reading the code, it is clear that this design choice posed a huge headache for Swing class developers. For SwingJS, we decided from the beginning NOT to allow this mixed-mode programming and instead to require that all components be Swing components. However, this is not really an issue. We have reconfigured the class relationships a bit. In SwingJS, all AWT components are now subclasses of javax.swing.JComponent. While this might seem error prone, so far we have found no problem with this arrangement. It's a little surprising to me that the original developers of Swing did not think of this. The a2s Adapter Package ----------------------- Originally, we thought that we would restrict ourselves to JApplets only. That is, only Swing-based applets. But as we worked, we discovered that there are a lot of great applets out there that are pre-Swing pure-AWT java.applet.Applet applets. Our problem was that we also wanted it to be possible to quickly adapt these applets to JavaScript as well. The solution turned out to be simple: Write a package (a2s) that recreates the interface for non-Swing components as subclasses of Swing components. Thus, a2s.Button subclasses javax.swing.JButton but also accepts all of the methods of java.awt.Button. This works amazingly well, with a few special adaptations to the core javax.swing to be "AWT-aware." Then, to tie it all togeter, all AWT components such as java.awt.Button now subclass their respective a2s components, which in turn subclass JComponents. So no changes in code are necessary. We have successfully transpiled over 500 applets using this strategy. Working with Files ================== Simple String file names are not optimal for passing information about files read by SwingJS applications. That is because just peeking at a file in SwingJS will load its entire byte[] data. Optimally, all work with files should either use Path or File objects exclusively. These objects, after a file is read or checked for existence, will already contain the file byte[] data. The string name can be used alone, since SwingJS will cache the files itself and not reload them -- just as the browser normally does. SwingJS uses the following criteria to determine if File.exists() returns true: (1) if this File object has been used directly to read data, or (2) if reading data using this File object is successful. Note that you cannot check to see if a file exists before input or if it was actually written or if it already exists prior to writing in SwingJS. Thus, you should check each use of file.exists() carefully, and if necessary, provide a J2sNative block that gives an appropriate "OK" message, for example: (/** @j2sNative 1 ? false : */ outputfile.exits()) or (/** @j2sNative 1 ? true : */ inputfile.exits()) Temporary files can be created in SwingJS. SwingJS will maintain a pseudo-filesystem for files created with File.createTempFile(). This is useful in that closure of writing to a temporary file does not generate a pseudo-download to the user's machine. Temporary files will be placed in the "/TEMP/" directory, as seen from the running Java program. Any file written to this directory will simply be stored in memory; files written to any other directory, when closed, will appear to the user as a download, often involving a "What do you want to do with this file" dialog. See below for details relating to each of the subjects below: UNIMPLEMENTED CLASSES BY DESIGN =============================== The SwingJS implementation of the following classes are present in a way that gracefully bypasses their functionality: accessibility security serialization TODO LIST FOR UNIMPLEMENTED CLASSES =================================== none as of 2020.12.31. Source code for classes and methods missing from Java 8 or from Java 9+ can be inserted by any developer along with their running code source, and they should run. MINOR ISSUES--required some rewriting/refactoring by Bob and Udo ================================================================ Thread.currentThread() == dispatchThread MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS ===================================================================== See below for a full discussion. primitive type restrictions - int, long, and float HashMap, Hashtable, and HashSet iterator ordering interning, new String("xxx") vs "xxx" Names with "$" and "_" ArrayIndexOutOfBounds java.awt.Color native methods javax.swing.JFileDialog key focus LookAndFeel and UI Classes System.exit(0) does not stop all processes list cell renderers must be JComponents myClass.getField not implemented "window" and other reserved JavaScript names reserved field and method names qualified field and method names Component.getGraphics(), Graphics.dispose() Graphics.setClip() MAJOR ISSUES--for Bob and Udo within SwingJS ============================================ fonts OS-dependent classes AWT component peers some aspects of reflection MAJOR ISSUES--to be resolved by implementers ============================================ fonts threads modal dialogs image loading no format internationalization Graphics2D: missing winding rules text-related field implementation Formatter/Regex limitations ======================================================================== DISCUSS ======= Table row/col sorter needs checking after removal of java.text.Collator references ========================================================================== ////////////////////////////////////////////////////////////////////////////// UNIMPLEMENTED CLASSES ===================== accessibility ------------- All Accessibility handling has been commented out to save the download footprint. This removes the need for sun.misc.SharedSecrets as well. Nothing says we could not implement accessibility. We just didn't. security -------- All JavaScript security is handled by the browser natively. Thus, Java security checking is no longer necessary, and java.security.AccessController has been simplified to work without native security checking. Note that private methods in a class are REALLY private. serialization ------------- All serialization has been removed. It was never very useful for Swing anyway, because one needs exactly the same Java version to save and restore serialized objects. keyboard accelerators and mnemonics ----------------------------------- This work was completed in the spring of 2019. Note that in a browser, some key strokes, particularly CTRL-keys, are not available. Bummer. MINOR ISSUES--required some rewriting/refactoring by Bob and Udo ================================================================ Thread.currentThread() == dispatchThread ---------------------------------------- changed to JSToolkit.isDispatchThread() MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS ===================================================================== primitive restrictions - int, long, and float --------------------------------------------- int For performance reasons, int addition and multiplication do not by default overflow to negative values. Instead, they just get bigger. Java code that relies on overflow to negative values should be surrounded by ()|0 -- an OR with integer 0: int bigI, bigJ; ... bigI = (bigI + bigJ)|0; bigI = (bigI + 1)|0; //instead of bigI++ Thus, in Java, the following is true: 2000000000 + 2000000000 == -294967296 But in SwingJS, that will be 4000000000. So, for example, the following strategy will fail in SwingJS: int newLength = lineBuf.length * 2; if (newLength < 0) { newLength = Integer.MAX_VALUE; } This is because, generally, "-1" in JavaScript is not 0xFFFFFFFF. The simple ()|0 takes caes of this: int newLength = (lineBuf.length * 2)|0; if (newLength < 0) { newLength = Integer.MAX_VALUE; } JavaScript does process bitwise operators & | ^ ~ properly for int values. There is no issue using these operations. Note that int 1/0 in Java throws "java.lang.ArithmeticException: / by zero", but in JavaScript is just Infinity. Importantly, the JavaScript Int32Array does behave properly. From the Firefox developer console: >> x = new Int32Array(1) <- Int32Array(1) [ 0 ] >> x[0] = 4000000000 <- 4000000000 >> x[0] <- -294967296 Notice that, perhaps unexpectedly, the following two constructs produce different results in JavaScript: x = new Int32Array(1); b = x[0] = 4000000000; (b will be 4000000000) and x = new Int32Array(1); x[0] = 4000000000; b = x[0]; (b will be -294967296) SwingJS leverages array typing to handle all byte and short arithmetic so as to ensure that any byte or short operation in JavaScript does give the same result in Java. long Java's 64-bit long type is fully supported, starting with java2script 3.3.1 (2020.12.31) The transpiler handles all conversions to and from long appropriately. See the discussion at https://github.com/BobHanson/java2script/issues/202 for how this is done. float SwingJS does not distinguish between float and double. Everything is double. HashMap, Hashtable, and HashSet iterator ordering ------------------------------------------------- In Java, iterators for HashMap, Hashtable, and HashSet do not guarantee any particular order. From the HashMap documentation for Java 8: This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time. Likewise, for HashSet (because it is simply a convenience method for HashMap