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 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.
All Accessibility handling has been commented out to save the download footprint.
This removes the need for sun.misc.SharedSecrets
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.
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.
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 &, |, 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
recode to JSToolkit.isDispatchThread()
SwingJS implements the native browser LookAndFeel. Token UIManager methods are
present but unimplemented; methods returning key bindings or other arrays return null.
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)) .....
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[]
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.
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!
}
See note under primitive types, above.
ColorSpace: only "support" CS_sRGB
-BH: This is a temporary edit just to get us started.
Not implemented. HTML5 cannot expose a file directory structure
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.
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
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.
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
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.
Glyph/composite/outline fonts are not supported
The only AWT components implemented are Dialog, Frame, Panel, and Window
They are subclassed as JDialog, JFrame, JPanel/JApplet, and JWindow, respectively
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
}
}
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
BigInteger and BigDecimal are not supported
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.
Not surprisingly, there are some bugs in the J2S compiler. These can be worked around with a bit of Java refactoring and style adjustment.
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.
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
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.
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.
for some reason HashMap cannot call its superclass (AbstractMap) "putall" method from its constructor.
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
}
}
...
J2S does not properly do integer/long/short/byte /=
n /= 3
needs to be written out as
n = n / 3
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. </container>
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();
}
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()
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.
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.
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 })
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);
...
}
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...