Java Method Overloading
and LiveConnect 3

Author: Scott Furman <fur@geocast.com>

Last modified:

1 Motivation

The technique that earlier versions of LiveConnect use to invoke overloaded Java methods from JavaScript is dirt-simple: The first applicable method that is enumerated by the Java VM is chosen.  (Here, "applicable" means that the method name and the number of arguments match and that each of the JavaScript arguments can be converted to the corresponding Java type listed in the method's signature.)

The Netscape JVM always enumerated methods using the order in which they appeared in their classfile, so rearrangement of methods in the Java source files was often required to invoke the desired method.  This behavior was sometimes painful to developers, both because source was not always available and because the static nature of this method resolution algorithm sometimes made it impossible to choose a different method resolution at each invocation site.  Most importantly, the willingness of LiveConnect to convert method arguments from JavaScript types to wildly different Java types sometimes lead to unexpected method resolutions.  A more serious problem has cropped up recently with Netscape's migration to 3rd-party JVM's: the enumeration order of methods is not defined by the Java specification, so overloaded method resolution can occur differently depending on which vendor's JVM is being used in conjunction with LiveConnect.  It is these latter two difficulties which are addressed by this proposal for LiveConnect version 3 (LC3).

2 Introduction

Ideally, invocation of Java methods from JavaScript would make use of Java's own rules for overloaded method resolution, but using JS's runtime types rather than Java's compile-time types.  However, that's not feasible for several reasons:

First, Java method signatures distinguish between a variety of numeric types, i.e. byte, char, short, int, long, float, double.  JavaScript collapses all numeric types, whether integral or floating point, into a single number type.  Given this Java class declaration:

class Ambiguous {
   static public int numericArg(int x)   { return 1; }
   static public int numericArg(byte x)  { return 2; }
   static public int numericArg(float x) { return 3; }
}
Which method should be invoked when integralArg is invoked from JavaScript ?
Packages.Ambiguous.numericArg(3);
Finally, there is precedent set by previous versions of LiveConnect which are all too willing to convert JavaScript arguments to unrelated Java types, e.g. the conversion of a JavaScript boolean value to a string or an instance of java.lang.Boolean.

3 Resolution of Java Methods from JavaScript

Unfortunately, there is no way to both completely preserve backward compatibility and cure LiveConnect of its method invocation ills.  The new approach is to apply heuristics to guess the intended method given the runtime JavaScript argument types and the type signatures of the candidate Java methods. Informally, the method with Java parameter types that most closely match the JavaScript types is chosen.  For example, when converting from a JavaScript number type, a method that specifies a double argument is preferred to one that requires a java.lang.String.

Although the the choice of method to be invoked may be different in LC3 compared to earlier versions of LiveConnect, the permitted conversions of JavaScript arguments to Java types has not been changed.  Hence, backward compatibility is preserved for invocations of non-overloaded methods or in cases where only a single method is compatible with the argument types used.

3.1 Method Accessibility and Applicability

The first step in resolving a method invocation is to determine which methods of a class are accessible and applicable.  A Java method is accessible and applicable if all of the following are true: If there are no applicable methods for an invocation, an error occurs. If there is only one applicable method, it is the one invoked.

3.2 Choose the Preferred Method

When choosing between two or more applicable methods, an algorithm is used that is similar in spirit to the ones used in Java and C++:
Suppose that U and S are both applicable methods for an invocation, each having n parameters.  Suppose, moreover, that the Java types of the parameters for method U are u1,...,un and the Java types of  the parameters for method S are s1,...,sn. Finally, the runtime JavaScript types of the actual arguments are t1,...,tn. Then the method U is preferred over method S iff
A method is said to be maximally preferred for a method invocation if it is applicable and there is no more preferred applicable method. If there is only one maximally preferred method, that method is necessarily preferred to all other applicable methods and it is the one invoked. If there is more than one maximally preferred method, an error occurs.

3.3 Allowed Method Argument Conversions

The following sections detail the allowed conversions of JavaScript values to Java values when converting arguments for method invocation.  These rules remain essentially unchanged from earlier LiveConnect implementations. If a conversion is not specifically listed below, then it is disallowed.

3.3.1 undefined

Java argument type
Conversion Technique
java.lang.Object
java.lang.String
"undefined"1
Note: Change in behavior.  In LC2 and previous implementations, conversion from undefined to a Java boolean type resulted in a value of false.
1There is some ambiguity to the result because the JS string literal "undefined" and the undefined JS value are both converted to the same Java string, but this wart is necessary to maintain backward compatibility with LC1.  Really, it would be best if conversion from JS undefined to all Java types caused an error.

3.3.2 Boolean

Java argument type
Conversion Technique
boolean
Map true/false directly to Java equivalent
java.lang.Boolean
java.lang.Object
Construct new instance of java.lang.Boolean.2
java.lang.String
true  ==> "true"
false ==> "false"
2Each argument conversion must result in a new java.lang.Boolean instance.  For example, it is not permitted to always use java.lang.Boolean.TRUE and java.lang.Boolean.FALSE.

3.3.3 Number

Java argument type
Conversion Technique
double 
Transfer exact value to Java with
no rounding or loss of magnitude/sign.
java.lang.Double
java.lang.Object
Create new instance of java.lang.Double, transferring exact value to Java with no rounding or loss of magnitude/sign.
float

  • Round JS number to float precision.

  • Unrepresentably large values are converted to +/- infinity.
long
int
short
byte
char

  • Truncate JS number to integral value by eliminating fractional part.

  • NaN's or numbers with a magnitude too large to be represented in the target integral type result in a runtime error.3
java.lang.String
Convert number to string per ECMA 9.8.1, ToString() applied to Number type

3In pre-LC3 versions of LiveConnect, conversion from either NaN's or numbers with a magnitude too large to be represented in the target integral type was ill-defined and had platform-dependent behavior.

3.3.4 Strings

Java argument type
Conversion Technique
java.lang.String
java.lang.Object
Convert from Unicode JS
string to Unicode java.lang.String
double5
float5
long5
int5
short5
byte5

  1. Convert string to number per ECMA 9.3.1

  2. Convert Result(1) to Java numeric type using rules in Section 3.3.3.
char5

  • For one-character strings, result is Unicode character.

  • Otherwise, convert to number, using rule immediately above.
  • 5Conversion added in LiveConnect version 2.

    3.3.5 Null

    Java argument type
    Conversion Technique
    Any class or interface type
    null

    3.3.6 Object

    3.3.6.1 JavaObject (A Java object wrapped inside a JS object)

    Java argument type
    Conversion Technique
    Any interface or class that is assignment-compatible with the Java object obtained by unwrapping the JS object, i.e. the unwrapped JavaObject is an instanceof() the Java argument type.
    Unwrap JS object to obtain Java object
    java.lang.String
    Call the unwrapped object's toString() method and return the result as a new java.lang.String.
    float, double, byte, char, short, int, long5

    1. If the object does not have a doubleValue() method, call the object's toString() method and convert using the rules in Section 3.3.4.

    2. Call object's doubleValue() method.

    3. Convert Result(2) to Java numeric type using rules in Section 3.3.3.

    3.3.6.2 JavaArray (A Java array wrapped inside a JS object)

    Java argument type
    Conversion Technique
    Any interface or class that is assignment-compatible with the Java object obtained by unwrapping the JS object, i.e. the unwrapped JavaObject is an instanceof() the Java argument type.
    Unwrap JS object to obtain Java object
    java.lang.String
    Call the unwrapped array's toString() method and return the result as a new java.lang.String.

    3.3.6.3 JavaClass

    Java argument type
    Conversion Technique
    java.lang.Class5
    Extract corresponding Java class object
    java.lang.JSObject
    java.lang.Object
    Wrap JS object in new instance of java.lang.JSObject
    java.lang.String
    Call the JavaClass toString() method and return the result as a java.lang.String.

    3.3.6.4 JavaScript Array

    Java argument type
    Conversion Technique
    java.lang.JSObject
    java.lang.Object
    Wrap JS object in new instance of java.lang.JSObject
    java.lang.String
    Call the JS object's toString() method and return the result as a java.lang.String.
    Any Java array type

    Create a new Java array of the appropriate type with a length equal to that of the JS Array object. Fill in each element of the Java array by converting each element of the JS array, including undefined elements, to an equivalent Java value using the method invocation conversion rules. If conversion is not possible for any single element of an array, the method invocation results in an error. Note: Since the contents of the JS array are copied, side-effects made by the invoked Java method to the Java array will not be reflected in the JS array argument.

    3.3.6.5 Other JavaScript Objects

    Java argument type
    Conversion Technique
    java.lang.JSObject
    java.lang.Object
    Wrap JS object in new instance of java.lang.JSObject
    java.lang.String
    Call the JS object's toString() method and return the result as a java.lang.String.
    float, double, byte, char, short, int, long5

    1. Apply the ToPrimitive operator (ECMA 9.3) to the JavaScript object with hint Number.

    2. Convert Result(1) to Java numeric type using rules in Section 3.3.3.

    3.4 Preferred Argument Conversions

    When converting from JavaScript to Java types, certain conversions are more "natural" and, hence, are preferred.   In the table below, Java types are listed in decreasing order of preference.  Types that are equally preferred are in the same table cell.

    3.4.1 undefined

    There is no preference among Java types for converting from the JavaScript undefined value.

    3.4.2 Boolean

    Java argument type,
    in decreasing order of preference
    boolean
    java.lang.Boolean
    java.lang.Object 
    java.lang.String

    3.4.3 Number

    Java argument type,
    in decreasing order of preference
    double 
    java.lang.Double
    float
    long
    int
    short
    char
    byte
    java.lang.String
    java.lang.Object

    Rationale: The preference for floating-point types over integral types is likely to be the largest culprit in exposing incompatibilities with earlier versions of LiveConnect.  However, double is the only primitive Java type guaranteed not to overflow or lose precision when converting from a JS number, so it should be preferred to the other Java numeric types.

    3.4.4 Strings

    Java argument type,
    in decreasing order of preference
    java.lang.String
    java.lang.Object
    char
    double, float, long, int, short, byte

    3.4.5 Null

    There is no preference among Java types for converting from the JavaScript null value.

    3.4.6 Object

    3.4.6.1 JavaObject (A Java object wrapped inside a JS object)

    Java argument type,
    in decreasing order of preference
    Any class or interface type6
    java.lang.String
    double
    float
    long
    int
    short
    char
    byte
    6Among Java reference types, further processing is required.  Intuitively, the rule for preference among Java types when converting from a Java object that is wrapped in a JS object is that the most specific class or interface is preferred.  More formally, let T be the  Java class of an unwrapped JavaObject. Let S and U be class or interface types.  S is preferred to U iff

    3.4.6.2 JavaArray (A Java array wrapped inside a JS object)

    Java argument type,
    in decreasing order of preference
    Any class or interface type6
    java.lang.String

    3.4.6.2 JavaClass

    Java argument type,
    in decreasing order of preference
    java.lang.Class
    java.lang.JSObject
    java.lang.Object
    java.lang.String

    3.4.6.3 JavaScript Array

    Java argument type,
    in decreasing order of preference
    Any Java array type
    java.lang.JSObject
    java.lang.Object
    java.lang.String

    3.4.6.4 Other JavaScript Objects

    Java argument type,
    in decreasing order of preference
    java.lang.JSObject
    java.lang.Object
    java.lang.String
    double
    float
    long
    int
    short
    char
    byte

    4 Explicit Method Specification

    LC3 allows explicitly specifying a particular method within a set of overloaded methods, thus bypassing the resolution process described in Section 3. Explicit method specification is most often used when a Java method is overloaded using Java numeric types as arguments:
    class Ambiguous {
       static public int numericArg(int x, byte y) { return 1; }
       static public int numericArg(byte x, char y) { return 2; }
       static public int numericArg(float x, int y) { return 3; }
    }
    In this case it is possible to specify that numericArg(int,byte) should be called using the following syntax:
    intNumericArg = Packages.Ambiguous["numericArg(int,byte)"];
    intNumericArg(5); // returns 1
    By using named property access and passing the name of the method with type signature information, an object will be returned that can be used to call the desired method.  Explicit method specification can be used on both instance and static methods.

    The same effect can be achieved without using a temporary value to hold the method:

    Packages.Ambiguous["numericArg(int,byte)"](5); // returns 1
    A similar syntax can be used to explicitly specify selection of an overloaded constructor.  For example, the following code invokes the java.lang.String constructor that accepts one argument, a one-dimensional array of characters:
    new java.lang.String["(char[])"](c);