Menu

Java-To-JavaScript Considerations

Bob Hanson

Introduction

This document addresses Java design that optimizes success in working with J2S. This implementation of J2S involves use of the jQuery and JSmol JavaScript libraries. All such required library code is included in this project's source code. (see jsmoljs/). The standard jQuery library (10.4+) can be used, but that has been extended to work with binary file transfer (see jsmoljs/JSmoljQueryExt.js). In addition, the required J2S Eclipse plug-in is in the project code under j2s/eclipse-plugin.

J2S Native Block

J2S allows documentation-block code annotations to replace snippets of Java code with raw JavaScript when compiling. These are set off using the @j2sNative annotation:

        /**
         * @j2sNative
         * 
         *  jQuery.$ || (jQuery.$ = jQuery);  return jQuery;
         */
                {
                  <replaced Java code here>
                }

Critical (and annoyingly easy to forget) is the necessary { ... } block after the j2sNative block. If that is left out, no replacement is made.

If return is used in this JavaScript, it is recommended to always use "return(xxx)" with no space between "return" and "(". Otherwise, when Eclipse reformatting is carried out using ALT-S-N or Alt-S-F it is possible to have something like this:

        /**
         * @j2sNative
         * 
         *  jQuery.$ || (jQuery.$ = jQuery);  return jQuery;
         */

turn into this:

        /**
         * @j2sNative
         * 
         *           jQuery.$ || (jQuery.$ = jQuery);  return
         *           jQuery;
         */

which will fail. But if we have

        /**
         * @j2sNative
         * 
         *           jQuery.$ || (jQuery.$ = jQuery);  return(jQuery);
         */

then "return(jQuery)" will always be kept as a single unit.

MINOR ISSUES--requiring no rewriting

accessibility

All Accessibility handling has been commented out to save the download footprint.
This removes the need for sun.misc.SharedSecrets

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.

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.

MINOR ISSUES--requiring some rewriting/refactoring by Bob and Udo

java.util.BitSet must be 16-bit

Although JavaScript will support numerical values up to 2^54,
these "long" values are really doubles. So we use a 16-bit BitSet. For efficiency, javajs.util.BS is recommended.

bit-wise operations force int

Bit-wise &, |, and !, <<, etc. on values greater than 2^30 do not give the
expected results because integer bit-wise operation is used.

Math.pow(2,31)
2147483648
Math.pow(2,31)&Math.pow(2,31)
-2147483648

Thread.currentThread() == dispatchThread

recode to JSToolkit.isDispatchThread()

MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS

LookAndFeel

SwingJS implements the native browser LookAndFeel. Token UIManager methods are
present but unimplemented; methods returning key bindings or other arrays return null.

primitive numerical types

JavaScript cannot distinguish among primitive number types
int, short, float, and double. Bob's J2S fix does allow for
distinguishing between int[] and float[], but that is all.
The implication is for overloaded methods and constructors.
For example:

  Color(int r, int g, int b, int a)
  Color(float r, float g, float b, float a)

cannot be distinguished. A very important case in this regard is

  StringBuffer.append(int)
  StringBuffer.append(float)

There is no way to know one's intent here. Integers must be changed to strings:

  sb.append("" + i)

This will need careful checking and will be the source of bugs, for sure,
because it is next to impossible to find all of these, and even if they are
found, certain cases such as above will never be perfectly resolved.

In addition, JavaScript does not/cannot support "long".
One cannot test against Long.MAX_VALUE or Long.MIN_VALUE.

Since int, long, byte, and char are not really integers, care must be taken
to avoid a loop such as the following, which was in java.text.DigitList:

  while (source > 0) {
    ... 
    source /= 10 
  }

Similarly, large integers will never roll over to negative ones. They will
just get bigger.

        int newLength = lineBuf.length * 2;
        /**
         * @j2sIgnore
         */
         {
            // never going to happen in JavaScript
            if (newLength < 0) {
              newLength = Integer.MAX_VALUE;
            }
         }

Also, J2S does not translate properly

  int n = 110
  n /= 100  

This needs to be recast as

  n = n / 100

For example, SimpleDateFormat needs

month = Clazz.doubleToInt(month / 367);

instead of

month /= 367

Because "-1" in JavaScript is not 0xFFFFFFFF one must take care to not compare a negative number with a 32-bit mask;

(b & 0xFF000000) == 0xFF000000

is true in Java for (int) b = -1, but is false in JavaScript, because 0xFF000000 is 4278190080,
while (-1 & 0xFF000000) is, strangely enough, -16777216, and, in fact,

(0xFF000000 & 0xFF000000) != 0xFF000000

because -16777216 is not 4278190080.

One must compare similar operations:

if ((b & 0xFF000000) == (0xFF000000 & 0xFF000000)) .....

distinguishing arrays

J2S cannot distinguish array types.
One cannot test:

  if (x instanceof float[]) {
  } else if (x instanceof double[]) {
  } else if (x instanceof Point2D[]) {
  }

The javajs.util.AU class does provide a set of tests for specific
kinds of arrays, but this is still not perfect, and it is not complete.
These methods call Clazz.isAxx methods and depend upon the first
inner-most array and, in some cases, inner-most array itself not being null.

  public static boolean isAB(Object x)     // byte[]
  public static boolean isAI(Object x)     // int[]
  public static boolean isAII(Object x)    // int[][]
  public static boolean isAF(Object x)     // float[]
  public static boolean isAFF(Object x)    // float[][]
  public static boolean isAFFF(Object x)   // float[][][]
  public static boolean isAD(Object x)     // double[]
  public static boolean isADD(Object x)    // double[][]

  public static boolean isAFloat(Object x) // Float[]
  public static boolean isAS(Object x)     // String[]
  public static boolean isASS(Object x)    // String[][]
  public static boolean isAP(Object x)     // javajs.util.T3[]

forced null typing

The J2S compiler has support for proper method signature handling
in cases such as:

  new URL((URL) null, filePath, null)

The issue here is that without that (URL) the call is ambiguous.
However, there is a flaw in j2slib (jmolj2s) in that the called
method receives a "null" URL object, which does not in JavaScript
evaluate to true for "== null". Thus, testing for null within
functions called this way process improperly.

native classes

There are no native classes. Any such class needs to be implemented in Java with or without calls to @j2sNative code.

The J2S compiler ignores all static native method declarations.
Anything of this nature needs to be implemented in JavaScript if it is needed,
using j2sNative blocks:

/**
 * @j2sNative
 *
 */
 {
   // it is critical to remember to insert this { } phrasing, 
   // or the block will be ignored!
 }

java.awt.Color

See note under primitive types, above.

ColorSpace: only "support" CS_sRGB

-BH: This is a temporary edit just to get us started.

javax.swing.JFileDialog

Not implemented. HTML5 cannot expose a file directory structure

key focus

  • Less explicit key focus
  • handling (mostly done by Browser/HTML)
  • no SwingUtilities.findFocusOwner

static methods and classes -- order is important

J2S cannot run a static method that calls an uninitiated static variable:

    static int x = createX();

    static int createX() {
        return y;
    }

    static int y = 20;

will fail. It's actually quite odd that it does not fail in Java as well.

Likewise, static classes hidden in other classes must be presented in an order
that does not call an uninitiated class. In java.util.Collections, for example,
it was important to move SynchronizedRandomAccessList to after SynchronizedList.

inner class order

For J2S, inner classes must not call other inner classes defined after them in a file.

This showed up in java.awt.geom.Path2D.Float.CopyIterator, which extends
java.awt.geom.Path2D.Iterator. Since the Iterator is in the code after CopyIterator,
the reference to java.awt.geom.Path2D.Iterator in

    c$ = Clazz.decorateAsClass (function () {
        this.floatCoords = null;
        Clazz.instantialize (this, arguments);
    }, java.awt.geom.Path2D.Float, "CopyIterator", java.awt.geom.Path2D.Iterator);

is null, and then CopyIterator does not extend Iterator.

Solution is to simply move the inner class definitions in the .java file

MAJOR ISSUES--for Bob and Udo within SwingJS

fonts

Fonts and FontMetrics will all be handled in JavaScript. Font matching will
not be exact, and composite (drawn) fonts will not be supported.

Jmol handles calls such as font.getFontMetrics(g).stringWidth("xxx") by
creating an off-page DOM image and querying its context.
This will be done here as well.

OS-dependent classes

Static classes such as:

   java.awt.Toolkit
   java.awt.GraphicsEnvironment

which are created using Class.forName
will be implemented as JavaScript classes in the swingjs package

AWTAccessor and AwtContext are not implemented

AWT component peers

ComponentPeer is a class that represents a native AWT component.
Components with such peers are called "heavy-weight" components.
They are expected to do the dirty work of graphics drawing.

Java Swing implements peers only for JApplet, JDialog, JFrame, and JWindow.
References to such objects have been removed, but clearly there must be
some connection to similar DOM objects, even for "light-weight" components.

MAJOR ISSUES--to be resolved by implementers

fonts

Glyph/composite/outline fonts are not supported

specific AWT components not implemented

The only AWT components implemented are Dialog, Frame, Panel, and Window
They are subclassed as JDialog, JFrame, JPanel/JApplet, and JWindow, respectively

threads

Thread locking and synchronization are not relevant to JavaScript.
Thus, anything requiring "notify.." or "waitFor.." could be a serious issue.

All threading must be "faked" in JavaScript. Specifically not available is:

  Thread.sleep()

javax.swing.AbstractButton#doClick(pressTime) will not work, as it requires Thread.sleep();

However, java.lang.Thread itself is implemented and used extensively.

Methods thread.start() and thread.run() both work fine.

In addition, SwingJS provides swingjs.JSThread, which can be subclassed
if desired. This class allows simple

  while(!interrupted()){
    wait()
    ...
  }  

action through an asynchronous function run1(mode). For example:

    protected void run1(int mode) {
        try {
            while (true)
                switch (mode) {
                case INIT:
                    // once-through stuff here
                    mode = LOOP;
                    break;
                case LOOP:
                    if (!doDispatch || isInterrupted()) {
                        mode = DONE;
                    } else {
                        Runnable r = new Runnable() {
                            public void run() {
                                // put the loop code here
                            }
                        };
                        dispatchAndReturn(r);
                        if (isJS)
                            return;
                    }
                    break;
                // add more cases as needed
                case DONE:
                    // finish up here
                    if (isInterrupted())
                        return;
                    // or here
                    break;
                }
        } finally {
            // stuff here to be executed after each loop in JS or at the end in Java
        }
    }

image loading

  • while JavaScript does not allow synchronous image loading, the JSmol framework gets around this by loading the binary image data synchronously and then using the data URI to deliver it synchronously to an Image element.

  • BufferedImage: only "support" imageType RGB and ARGB

-BH: This is a temporary edit, just to get us started. Certainly GRAY will be needed

no BigInteger; no BigDecimal

BigInteger and BigDecimal are not supported

no format internationalization

For now, just en for number and date formatters

Text fields are:

JTextField   (JavaScript <input type="text">)
JTextArea    (JavaScript <textarea>; TODO)
JTextPane    (JavaScript <div>; non-editing; TODO)
JEditorPane  NOT IMPLEMENTED

For the initial implementation, we don't implement infinite undo/redo, and the abstract
document model is much less elaborate. Only PlainDocument (in the form of JSPlainDocument)
is implemented.

Thus, the Document returned by JTextField.getDocument() is a javax.swing.text.Document, but it
is a swingjs.JSPlainDocument (extending swingjs.JSAbstractDocument)
rather than a javax.swing.text.PlainDocument (extending javax.swing.text.AbstractDocument).

All scrolling is handled by HTML5.

javax.swing.AutoScroller is not implemented

public static methods .stop, .isRunning, .processMouseDragged require true Java threading
javax.swing.text.View and its subclasses are not implemented.

The JS document model does not allow two fields to address the same underlying document.

J2S bugs

Not surprisingly, there are some bugs in the J2S compiler. These can be worked around with a bit of Java refactoring and style adjustment.

1 forced typing in method signatures

The J2S compiler does not have support for proper method signature handling in cases such as:

  new URL((URL) null, filePath, null)

where the forced typing of null indicates which overloaded method to invoke. The issue here is that without "(URL)" the call is ambiguous.
However, there is a flaw in j2slib (jmolj2s) in that the called
method receives a "null" URL object, which does not in JavaScript
evaluate to true for "== null". Thus, testing for null within
functions called this way process improperly.

For example, in javax.swing.RepaintManager:

    public static RepaintManager currentManager(JComponent c) {
        return currentManager((Component) c);
    }

This causes an infinite loop.

2 multi-level empty array declarations

array declarations

 int[][] a = new int[3][]

and

 int[] a = new int[3]

are treated the same. The first needs to be changed to javajs.util.AU.newInt2(3)
Several similar methods are in javajs.util.AU

3 inner class variable renaming

The J2S compiler changes the variable names in inner classes variables to a b c d..., making it
nearly impossible to insert @j2sNative blocks. As much as possible, remove inner classes, and if they are necessary, make sure they call methods in their outer classes if @j2sNative is used.

4 calling ResourceBundle

J2S supports run-time loading of resource bundles, but there is a bug in that implementation by the compiler.

@J2SRequireImport({jsjava.util.PropertyResourceBundle.class})

is required for public abstract class ResourceBundle because the inner class
ResourceBundle.Control requires it, but for some reason it is not included in the
MUST list in the Clazz.load() call.

5 HashMap oddity

for some reason HashMap cannot call its superclass (AbstractMap) "putall" method from its constructor.

6 inner class constructors lost

J2S drops seemingly unnecessary constructors in inner classes. These
are required, and must be included. I added j2sNative blocks to do this.

public class Gregorian extends BaseCalendar {

    static class Date extends BaseCalendar.Date {
      protected Date() {
        super();
        /**
         * @j2sNative
         */
        {
        int dummy = 1; // forces J2S to leave this in
        }
    }

    protected Date(TimeZone zone) {
        super(zone);
        /**
         * @j2sNative
         */
        {
        int dummy = 1; // forces J2S to leave this in
        }
    }

  ...

7 integer i /= n does not work

J2S does not properly do integer/long/short/byte /=

  n /= 3

needs to be written out as

  n = n / 3

8 class-instantiation may not load required classes properly

There are times where J2S does not execute initialization in the proper order
when constructing subclasses. The problem is only occasionally seen, and it always
occurs when "new" is used in a class outside of methods. Usually this is fine, but
it can result in problems.

For example, in the creation of JApplet, the ArrayList<Container>.component constructor
was cleared in Clazz.prepareFields for Container AFTER it was
used in the constructor for JApplet.

The way around this is to not instantiate Objects in-line:

public class Container
...
   List<Component> component =  new ArrayList();
...

but instead put that in the constructor.

public class Container
...
   List<Component> component;
...
public Container () {
   component =  new ArrayList();
}

9 arrays with no elements cannot be typed

array.getClass() returns "array" -- there is no way to determine the class of an array
if it has no elements. However a work-around is to use

     java.lang.reflect.Array.newInstance(xxxx.class, n)

which I have adjusted to allow for testing using .getClass().getComponentType()

10 Inner classes must not call other inner classes defined after them in a file.

This showed up in java.awt.geom.Path2D.Float.CopyIterator, which extends
java.awt.geom.Path2D.Iterator. Since the Iterator is in the code after CopyIterator,
the reference to java.awt.geom.Path2D.Iterator in
    c$ = Clazz.decorateAsClass (function () {
        this.floatCoords = null;
        Clazz.instantialize (this, arguments);
    }, java.awt.geom.Path2D.Float, "CopyIterator", java.awt.geom.Path2D.Iterator);
is null, and then CopyIterator does not extend Iterator.

Solution is to make sure inner subclasses only extend other inner subclasses that are already defined in the file.

11 instanceof can fail for local anonymous class definitions

in javax.swing.JSlider we have

  ...
    public Hashtable createStandardLabels( int increment, int start ) {
      ...
        class SmartHashtable extends Hashtable implements PropertyChangeListener {
           ...
            class LabelUIResource extends JLabel implements UIResource {
               ...
            }
            ...
            public void propertyChange( PropertyChangeEvent e ) {
               ...

                        if ( !(value instanceof LabelUIResource) ) {
                            hashtable.put( key, value );
                        }
                    }

               ...
            }

but for that last if check, we see in the JavaScript:

    if (!(Clazz.instanceOf (e, ))) {
      d.put (c, e);
    }

where "LabelUIResource" is missing. This can be fixed by moving both of these inner classes to inside the overall class, but not inside a method.

12 use of "new" static blocks may require explicit import

In java.awt.image.Raster, we have a static block that
creates new SinglePixelPackedSampleModel, IntegerInterleavedRaster, and ByteInterleavedRaster objects. In that case, we needed to add @J2SRequireImport annotations in order to force J2S to load those classes prior to loading this class.

      @J2SRequireImport({ jsjava.awt.image.SinglePixelPackedSampleModel.class,    jssun.awt.image.IntegerInterleavedRaster.class, jssun.awt.image.ByteInterleavedRaster.class })

13 A subclass cannot have the same constructor signature as a superclass except for

a parameter of its class:

public IntegerComponentRaster extends SunWriteableRaster {
    public IntegerComponentRaster(SampleModel sampleModel, DataBuffer dataBuffer,
            Rectangle aRegion, Point origin, Raster parent) {
            super(sampleModel, dataBuffer, aRegion, origin, parent);
            ...
        }

public SunWriteableRaster extends WriteableRaster {
    public IntegerComponentRaster(SampleModel sampleModel, DataBuffer dataBuffer,
            Rectangle aRegion, Point origin, SunWriteableRaster parent) {
            super(sampleModel, dataBuffer, aRegion, origin, parent);
            ...
        }

Notice how the signature changes in the last parameter, even though it is really the same signature. This results in an infinite loop.

Solution: there is no need for this; just make all references to the superclass:

    public IntegerComponentRaster(SampleModel sampleModel, DataBuffer dataBuffer,
            Rectangle aRegion, Point origin, Raster parent) {
            super(sampleModel, dataBuffer, aRegion, origin, parent);
            ...
        }

14 inner classes required by instanceof may not be loaded

J2S should not require loading a class just because it is the target of instanceof. If a class has not been loaded, nothing cannot be an instance of that class.

In any case, when an inner public class is called by another class using instanceof, that inner class becomes an "optional" load. But optional loads must still be loaded, and unless declared in package.js, J2S will look for xxx.xxx.Outer/Inner.js because the inner classes are not fully declared. For example,

   NumberFormat.Field x = new NumberFormat.Field();

in an inner class is going to cause the j2sLib to look for NumberFormat.Field. But unless NumberFormat has been loaded, NumberFormat.Field will not be found, because J2S will look for it in NumberFormat/Field.js, not NumberFormat.js.

Solution is to switch to requiring the outer class, not the inner class:

@J2SRequireImport(NumberFormat.class)
@J2SIgnoreImport(NumberFormat.Field.class)
public class NumberFormatter extends InternationalFormatter...