 |
 |
|
 |
 |
 |
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 Invocation 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:
-
The method is public.
-
If the invocation is a static invocation, the method must be a static method.
If the invocation is an instance invocation, the method must not be static.
-
The number of parameters in the method declaration equals the number of
argument expressions in the method invocation.
-
The type of each actual argument can be converted by LiveConnect method
invocation conversion (See Section 3.3).
If there are no applicable methods for an invocation, a NoSuchJavaMethod
exception is thrown. 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
-
uj and sj are the same type, or
-
conversion to type uj is preferred
to the conversion to type sj when converting from tj
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 AmbiguousJavaMethod
JavaScript exception is thrown.
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.
3.3.1 undefined
|
Java argument type
|
Conversion Technique
|
java.lang.Object
java.lang.String
|
"undefined"1
|
|
boolean
|
false (Should this result in a runtime
error instead ?)
|
double
float
|
NaN (Should this result in a runtime
error instead ?)
|
long
int
short
char
byte
|
0 (zero) (Should this result in a runtime
error instead ?)
|
1The java.lang.String resulting from the
conversion should be interned, so that it can be compared to other string
values using the == operator. There 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.
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" 3
false ==> "false"
|
double4
float4
long
int
short
char
byte
|
true ==> 1
false ==> 0
|
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.
3The java.lang.String resulting from the conversion
should be interned, so that it can be compared to other string values using
the == operator.
4The conversion to Java float and double from JS boolean
is probably not useful, but it's left in for backward compatibility with
LC1
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
|
-
Round JS number to integral value using round-to-negative-infinity mode.
-
Numbers with a magnitude too large to be represented in the target integral
type result in a runtime error.
-
NaNs are converted to zero. (Should NaN's result
in a runtime error instead ?)
|
|
java.lang.String
|
true ==> "true"3
false ==> "false"
|
|
boolean
|
0, NaN ==> false
all other values ==> true
|
3.3.4 Strings
|
Java argument type
|
Conversion Technique
|
java.lang.String
java.lang.Object
|
Convert from Unicode JS
string to Unicode java.lang.String5
|
double
float
long
int
short
byte
|
-
Convert string to number per ECMA 9.3.1
-
Convert Result(1) to Java numeric type using rules
in Section 3.3.3.
|
|
char
|
For one-character strings, result is Unicode character.5
Otherwise, convert to number, using above rule.
|
|
boolean
|
empty string ==> false
all other values ==> true
|
5Conversion added in LiveConnect version 2.
3.3.5 Null
|
Java argument type
|
Conversion Technique
|
|
Any class or interface type
|
null
|
double
float
long
int
short
byte
char
|
0 (zero)
|
|
boolean
|
false
|
3.3.6 Object
3.3.6.1 JavaObject/JavaArray
|
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.
|
3.3.6.2 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.3 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.
|
3.4 Preferred Argument Conversions
When converting from JavaScript to Java types, certain conversions are
more "natural" and, hence, are preferred.
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
|
|
long, int, short, char, byte
|
|
double, float
|
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
|
|
boolean
|
|
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
|
|
boolean
|
3.4.5 Null
Java argument type,
in decreasing order of preference
|
|
Any class or interface type
|
|
double, float, long, int, short, byte, char, boolean
|
3.4.6 Object
3.4.6.1 JavaObject/JavaArray
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
-
An instance of T is assignable to a variable of type S, i.e.
T
instanceof S is true
-
An instance of S is assignable to a variable of type U, i.e.
S
instanceof U is true
-
S and U are not the same types
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 Other JavaScript Objects
Java argument type,
in decreasing order of preference
|
|
java.lang.JSObject
|
|
java.lang.Object
|
|
java.lang.String
|
3.5 Explicit Method Specification
LC3 allows explicitly specifying an overloaded method and bypassing
the resolution process. Explicit method specification is typically
used when an Java method is overloaded using Java numeric types:
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;
}
}
In this case it is possible to specify that numericArg(int) should be
called using the following syntax:
intNumericArg = Packages.Ambiguous["numericArg(int)"];
intNumericArg(5); // returns 1
By using named property access and passing the name of the method with
type information, an object will be returned that can be
used to call the desired method.
The same effect can be achieved with this more compact syntax:
Packages.Ambiguous["numericArg(int)"](5); // returns 1
|
 |
 |