Feature #3867
direct java class access from 4GL code
100%
Related issues
History
#1 Updated by Greg Shah over 5 years ago
- Related to Feature #3751: implement support for OO 4GL and structured error handling added
#2 Updated by Greg Shah over 5 years ago
Once OO 4GL features are available, it is a natural extension to use the same syntax for Java class access. For example:
using java.util.* from java. using java.lang.* from java. def var hmap as HashMap. def var whatever as char init "yep". hmap = new HashMap(). hmap:put("something", whatever). if hmap:containsKey("nothing") then do: message "not here". end. System:out:println("statics should work too").
This is similar to how the 4GL supports direct .NET classes, except that there is no "bridge" needed since the converted code is already in the JVM and is real Java code. The other thing that the 4GL does is there is a implicit type conversion that occurs between the core 4GL and .NET data types.
Since we already have wrapper classes that can be used to pass 4GL data to Java, there is not a hard requirement for this. However, we may want to implement a similar facility so that any 3rd party code can be used in a simple way. If we don't do this, then we need to ensure that there are simple ways for the 4GL code to explicitly cast and/or convert data into the core Java types.
I would expect that much of the work for enabling this would be at parse time since we will need to dynamically resolve classes, methods, data members. This means that during parsing, any Java code that is accessed must be present in the classpath.
Conversion will have some changes as well to enable the rest of this. The runtime changes are minimal except for any extra features needed for casting/type conversion.
This will be a very powerful capability. It will enable direct integration of any Java technology.
This will also allow us to implement extensions that do not need to be written as handle-based resources. Things like our JasperReports or SMTP Email support can simply be Java code that is written for easy usage from converted 4GL code.
#3 Updated by Greg Shah over 5 years ago
Does anyone see any additional problems or effort needed that I missed above?
#4 Updated by Greg Shah over 4 years ago
- Status changed from New to WIP
- Assignee set to Greg Shah
I have started work on this. I'm planning to put the changes in 4069a.
#5 Updated by Greg Shah over 4 years ago
Status
4069a revision 11370 is the first set of changes related to this task.
- The parser and the
SymbolResolver
have been modified to add theKW_JAVA
as an alternative in theUSING
statement. When a Java class or package is referenced in aUSING
statement, the 4GL code MUST include aFROM JAVA
clause. Otherwise we will not treat thatUSING
statement as related to Java processing. Forcing this explicitly seems to be no real cost to future 4GL devs but it makes the 4GL code more clear and it ensures there is no conflict in searching with legacy 4GL/.NET cases. - The
SymbolResolver
has been modified to honor Java in class loading, class lookup, class name resolution and annotations. - Added a
JavaClassDefinition
as a subclass ofClassDefinition
and a helper classJavaFieldDefinition
as a subclass ofVariable
. These two honor the minimum API so that the parser andSymbolResolver
can operate properly with Java classes/methods/constructors/fields. The core of it uses reflection to dynamically resolve/lookup methods/constructors/fields, inspect/return data about them and process annotations. Stubbed out all code used just for handling the builtin 4GL classes or which can only be used from a 4GL class/interface/enum definition.
The method lookup is tricky. When doing our early lookup processing (i.e. before we have parsed the parameter list), we use JavaClassDefinition.guessMethod()
to find any method with the same name, access rights and static/instance. If we get at least one back, we return the first item in the list.
We handle annotations after we have a signature for lookup. In this case, I've created a multi-tier search algorithm (see JavaClassDefinition.lookupMethodWorker()
).
- Exact Matching - Convert the parameters into their direct Java class counterparts and see if there is an exact match (see
exactMethodLookup()
). - Fuzzy Matching (see
fuzzyMethodLookup()
)- Make a list those methods/constructors which match in name (methods only), number of parameters, access mode and static/instance. See
getCandidateMethods()
. - Iterate through that list and process each with
testFuzzyMatch()
matching logic.- This method iterates through every parameter and compares the required method parameter with one of the following caller's parameter (by order of precedence):
- The caller's type is the same exact type as the method parameter being checked.
- For a caller's type as
object extends something>
it checks if the method's parameter issomething
. - Is the method's parameter in the set of "near matches" which is the list possible boxing transformations for the caller's type?
- Is the method's parameter
isAssignableFrom()
the caller's type? - For a caller's type as
object extends something>
is the method's parameterisAssignableFrom()
thesomething
type?
- The number of
exact
(case 1),unwrap
(case 2),near
(case 3) andparent
(cases 4 and 5) matches is recorded. - If all parameters match in one of the 5 ways, then this is a match and a
FuzzyMatch
instance is returned with all the matching metrics. - Any non-null
FuzzyMatch
is added to aTreeSet<FuzzyMatch>
. This is a naturally sorted set andFuzzyMatch
implementsComparable
so that it has a natural sorting order (higher precedence sorts first):- The combined number of exact matches and unwrap matches.
- Near matches.
- Parent matches.
- The order in which the method was returned by Java reflection.
- This method iterates through every parameter and compares the required method parameter with one of the following caller's parameter (by order of precedence):
- After iteration through the candidates, if the
TreeSet
is empty then there is no fuzzy match. - If the
TreeSet
is non-empty, then the first entry is considered the best match and that is returned.
- Make a list those methods/constructors which match in name (methods only), number of parameters, access mode and static/instance. See
Design Points
- We support static and instance method calls.
- We support static and instance member field references.
- We require 4GL code to use case-sensitive names for all Java names (classes/interfaces, methods and fields).
- Only
INPUT
mode parameters are supported (you can leave off the mode or specifyINPUT
, but any use ofINPUT-OUTPUT
orOUTPUT
will generate an error). USING
- You must include
from java
in anyUSING
statement that references either a Java package or an explicit Java class. - All
USING
statements must be explicit. There is no implicitUSING java.lang.* FROM JAVA
. - There is no equivalent to the PROPATH search for a Java class. Any search from
USING some.package.* FROM JAVA
will add that package to the list of package prefixes that will be prepended to possible Java class names to try to get theClass>
instance for that name. We useClass.forName()
to obtain the class instance. This will lookup from whatever sources are in the Java classpath for theConversionDriver
. This is not the same idea as a PROPATH lookup which is inherently file-system based. - Any Java classes that are referenced must be present in the classpath of both conversion and the FWD server runtime.
- You must include
- Boxing/Unboxing
- The intention is to map the following to/from natural Java types:
- method return values
- method parameters
- member field access
- member field assignment
- Supported conversions:
character
java.lang.String
longchar
date
java.util.Date
datetime
datetimetz
datetime
java.sql.Timestamp
datetimetz
decimal
double
java.lang.Double
java.lang.BigDecimal
integer
int
java.lang.Integer
long
java.lang.Long
double
java.lang.Double
java.lang.BigDecimal
int64
long
java.lang.Long
java.lang.BigDecimal
logical
boolean
java.lang.Boolean
longchar
java.lang.String
raw
byte[]
- Both array and non-array forms should be handled.
- Non-array forms should be handled by helper methods in a given type (e.g.
integer.intValue()
). - Array forms will need static helper methods to convert at runtime. For example, a character extent var should be able to be passed as a
String[]
parameter (e.g.String[] c2s(character[] c)
).
- Non-array forms should be handled by helper methods in a given type (e.g.
- The intention is to map the following to/from natural Java types:
- No support for:
- generics
- varargs
- multi-dimensional arrays
- lambdas
Known Issues and Next Steps
- The example in #3867-2 parses everything except the
System:out:println(...).
code at the end. - Conversion of the method calls and member field access/assignment must be added.
- Boxing/Unboxing
- Method Parameters
- Parse time support is handled up to the annotations for methods, including leaving behind "wrap_parameter" and "classname" annotations.
- I suspect more work is needed here, to handle unwrapping of
object.ref()
forms. - Conversion support is not there yet.
- Runtime conversion helpers are not written yet.
- Method Return Value - nothing has been done on this yet.
- Member Field Usage - nothing has been done on this yet.
- Method Parameters
Future Steps
- Inheritance of OO 4GL class from Java class.
- Allow differentiating of name conflicts in an
AS
clause by adding theKW_JAVA
(e.g.def var i as java class Integer
would force a lookup of the Java class in this case). - Using an OO 4GL class type in Java code as a method parameter, method return type or field data type.
#6 Updated by Greg Shah over 4 years ago
The testcase in #3867-2 is checked in as testcases/uast/direct_java_usage_simple.p
.
#7 Updated by Constantin Asofiei over 4 years ago
- should the Java objects be defined as
java.util.HashMap
or via theobject extends java.util.HashMap>
? If we are not using BDTobject
, then we have problems with thefinal var can not be changed
issue, if you recall. And if we are using BDTobject
, then the runtime needs changes (can't tell how major these changes are), as i.e.object
class is defined asT extends _BaseObject_
- and it can't hold thejava.lang.Object
instances. - for
System.out.println
, the parser finds theprintln(java.lang.Object)
as a match, instead ofprintln(java.lang.String)
. I don't think this is a big problem, if the corresponding API exists (and it exists in this case), to unbox i.e.character
tojava.lang.String
. - do you want more complex examples than
direct_java_usage_simple.p
?
I still need to add the boxing/unboxing.
#8 Updated by Constantin Asofiei over 4 years ago
Another question is using Java-style vars in 4GL constructs. Think something like this:
def var ji as java.lang.Integer. do ji = 1 to 10: message ji. end.
How should this behave? It matters because is dependant how ji
gets converted: as object extends java.lang.Integer>
or as java.lang.Integer
.
#9 Updated by Constantin Asofiei over 4 years ago
Or the goal is to just access Java APIs (be it a Java method or a Java field, declared in some existing Java class), and not use the Java types directly from 4GL code?
#10 Updated by Greg Shah over 4 years ago
I can parse and convert (but not compile) your sample.
Exciting! How extensive were the changes for that?
I don't see any new revisions in 4069a. Are your changes safe to check in?
should the Java objects be defined as java.util.HashMap or via the object? If we are not using BDT object, then we have problems with the final var can not be changed issue, if you recall. And if we are using BDT object, then the runtime needs changes (can't tell how major these changes are), as i.e. object class is defined as T extends BaseObject - and it can't hold the java.lang.Object instances.
Hmm. Good point.
I want to avoid the object
wrapping and keep the access as clean and direct as possible. A solution would be to make all Java variables into business logic class members so they do not need to be final
. But for Java variables, this would mean that the normal variable scoping assumptions would be broken. The primary implication is for top level code blocks that are not external procedures (internal procedures, functions, methods, triggers, property getters/setters). Without extra code, these blocks could not duplicate variable names for Java vars. But we can resolve this by disambiguating any scoped vars that have naming conflicts.
Example: A Java var named hmap
is defined in the external procedure and a Java var named hmap
is defined in an internal procedure blah
, then we would create two different business logic class members, one named hmap
and the other named hmap_1
(or hmap_blah
or whatever).
Can you see any flaw in this idea?
for System.out.println, the parser finds the println(java.lang.Object) as a match, instead of println(java.lang.String). I don't think this is a big problem, if the corresponding API exists (and it exists in this case), to unbox i.e. character to java.lang.String.
Another good point. I had not planned to support varargs and then I put a varargs method into my "simple" example. Not my best moment. :)
Perhaps we should consider supporting varargs after all. I guess it may be an easy thing to encounter by accident.
do you want more complex examples than direct_java_usage_simple.p?
Yes, I think we should try more method and field variations to ensure the core functionality works.
How should this behave? It matters because is dependant how ji gets converted: as object or as java.lang.Integer.
object
would not help in the TO
clause of the DO
block. There is merit in allowing such usage but there are some cases which may need to be excluded. I can envision direct boolean
usage in if
statements and so forth, which actually should not be too hard. The problem here is that the control flow mechanism is assigning ji
internally. The fact that Integer
is immutable and it also doesn't mimic all the behavior (unknown etc..) of integer
means that boxing/unboxing can't help here. It will depend on the control flow mechanism. Some exclusions will be needed.
Or the goal is to just access Java APIs (be it a Java method or a Java field, declared in some existing Java class), and not use the Java types directly from 4GL code?
We definitely DO want to use the Java types directly in 4GL code but some control flow cases are special. Anything that can be handled with simple boxing/unboxing should "just work". I think the message ji
should work, but the do ji = 1 to 10
should not. I also expect that we should be able to pass these as parameters to converted 4GL code. I suspect the parameter helper code will need to changes to enable that.
We can accept limits (exclusions) on assignment of anything immutable.
#11 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
I can parse and convert (but not compile) your sample.
Exciting! How extensive were the changes for that?
Just some pinpointed fixes, for now. See 4069a rev 11371
Example: A Java var named
hmap
is defined in the external procedure and a Java var namedhmap
is defined in an internal procedureblah
, then we would create two different business logic class members, one namedhmap
and the other namedhmap_1
(orhmap_blah
or whatever).
Can you see any flaw in this idea?
The problem here is that any recursive call will be broken... we had this in the past, where we promoted local vars as instance fields, and it took us a while to figure out some bugs. I'd like to avoid promoting vars scoped to top-level blocks, as this breaks the var's lifetime completely.
Another good point. I had not planned to support varargs and then I put a varargs method into my "simple" example. Not my best moment. :)
I didn't mean varags, I meant that java.lang.Object
has priority when looking up a method, over the one which can match exactly (after unboxing).
object
would not help in theTO
clause of theDO
block. There is merit in allowing such usage but there are some cases which may need to be excluded. I can envision directboolean
usage inif
statements and so forth, which actually should not be too hard. The problem here is that the control flow mechanism is assigningji
internally. The fact thatInteger
is immutable and it also doesn't mimic all the behavior (unknown etc..) ofinteger
means that boxing/unboxing can't help here. It will depend on the control flow mechanism. Some exclusions will be needed.
Good points.
#12 Updated by Greg Shah over 4 years ago
The problem here is that any recursive call will be broken... we had this in the past, where we promoted local vars as instance fields, and it took us a while to figure out some bugs. I'd like to avoid promoting vars scoped to top-level blocks, as this breaks the var's lifetime completely.
Perhaps we need a jobject extends java.lang.Object>
that is a locally scoped final
instance which can be unwrapped/accessed, assigned and passed around. I guess it never gets passed as a parameter of type object
which the object
cases all derive from _BaseObject_
which is a separate hierarchy. I that is correct, then passing this as a parameter is about boxing/unboxing to the BDT primitive types OR for cases where the 4GL code is defined with parameter types that are Java objects outside of the _BaseObject_
hierarchy (e.g. java.util.HashMap
).
Can you see a flaw in my assumption that jobject
doesn't ever have to be passed to object
and vice versa?
I didn't mean varags, I meant that java.lang.Object has priority when looking up a method, over the one which can match exactly (after unboxing).
I intended the fuzzy method lookup to prioritize the near matches (boxing/unboxing) over the parent matches. This sounds like a bug.
#13 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
The problem here is that any recursive call will be broken... we had this in the past, where we promoted local vars as instance fields, and it took us a while to figure out some bugs. I'd like to avoid promoting vars scoped to top-level blocks, as this breaks the var's lifetime completely.
Perhaps we need a
jobject extends java.lang.Object>
that is a locally scopedfinal
instance which can be unwrapped/accessed, assigned and passed around. I guess it never gets passed as a parameter of typeobject
which theobject
cases all derive from_BaseObject_
which is a separate hierarchy. I that is correct, then passing this as a parameter is about boxing/unboxing to the BDT primitive types OR for cases where the 4GL code is defined with parameter types that are Java objects outside of the_BaseObject_
hierarchy (e.g.java.util.HashMap
).Can you see a flaw in my assumption that
jobject
doesn't ever have to be passed toobject
and vice versa?
I think this can work; the alternative is to create a i.e. java.util.HashMap[] someVar = new java.util.HashMap[1];
and use this someVar[0]
reference, to avoid the 'effective final' problem. We already do this for some cases of extents. But the code will be a little weird to read.
The other good point about using jobject
is that it makes the reference mutable, and we could enable OUTPUT/INPUT-OUTPUT
when passing this to 4GL methods (but not Java methods).
#14 Updated by Constantin Asofiei over 4 years ago
A limitation here is that any file system access, process launching, etc, is done from the FWD server process (and machine). I'm trying to find an example which would show how to use Java code from 4GL 'in real life'.
#15 Updated by Greg Shah over 4 years ago
Yes, this opens up potential security (and reliability) issues. I think we are offering this under the credo: "with great power comes great responsibility".
I'm trying to find an example which would show how to use Java code from 4GL 'in real life'.
A simple example might use trigonometric functions that are easily available in Java but missing from the 4GL.
For things like JasperReports integration or SMTP email support, we might have implemented differently if we had this support. Helper classes might be enough for some really powerful capabilities, by using this direct Java access.
#16 Updated by Constantin Asofiei over 4 years ago
Can we assume normal Java behaviour (e.g. NPE throw) for cases where the variable is uninitialized, and it is used in an expression, like ji + 1? Same for cases like if jl then message "yes, can do!".
I ask because assignments like pi = ji + 1 or ji = ji + 1 should be converted like pi.assign(ji.ref() + 1); and ji.assign(ji.ref() + 1));, but this will not work if we want to assign ji to null if one operand is null/unknown.
#17 Updated by Greg Shah over 4 years ago
I think we can assume normal Java NPE behavior, with some caveats.
- We will need to allow Java exceptions in 4GL catch blocks.
- At this time we haven't considered that pure Java operators would be used. This means that we are assuming that the converted 4GL operators would be in use.
pi = ji + 1
should be converted aspi.assign(plus(ji.ref(), 1));
and we should be handlingnull
safely in our operator methods, treating it as unknown value. So I actually would not expect an NPE in this case. - I guess assigning
unknown
to ajobject
should set the reference tonull
.
#18 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
- At this time we haven't considered that pure Java operators would be used. This means that we are assuming that the converted 4GL operators would be in use.
pi = ji + 1
should be converted aspi.assign(plus(ji.ref(), 1));
and we should be handlingnull
safely in our operator methods, treating it as unknown value. So I actually would not expect an NPE in this case.
I understand, but the problem here is that I need to convert all MathOps
APIs to use Java objects instead of native types (like java.lang.Double
instead of double
). But this will not automatically convert an int literal to a Double instance, if there is no explicit cast... so I need to add more APIs to treat each Java number type.
#19 Updated by Constantin Asofiei over 4 years ago
And also about using jobject.ref()
; for a call like Math:sin(jrad)
, where jrad
is java.lang.Double
, if this gets converted to Math.sin(jrad.ref())
and jrad
is unknown, I have no way to prevent the NPE.
I think I need to emit a non-null check for cases where a Java var is used in 4GL APIs (like MathOps.plus
), and a null check in cases where we pass this value to an API which requires a Java native value (like Math.sin
). This will allow me to raise an ERROR condition if the jobject instance is unknown, before passing this as argument.
#20 Updated by Greg Shah over 4 years ago
I think the 4GL operators would require wrapping the Java type in the correct BDT. Then the 4GL operators will work naturally.
#21 Updated by Greg Shah over 4 years ago
I think I need to emit a non-null check for cases where a Java var is used in 4GL APIs (like MathOps.plus),
Using BDT wrapping, a null should be treated like unknown value, which we already handle in the operators.
and a null check in cases where we pass this value to an API which requires a Java native value (like Math.sin). This will allow me to raise an ERROR condition if the jobject instance is unknown, before passing this as argument.
How about a variant of ref()
like refNonNull()
which will raise ERROR
if the reference is null
.
#22 Updated by Constantin Asofiei over 4 years ago
jrad = jdeg * Math:PI / 180. jsin = Math:sin(jrad). jcos = Math:cos(jrad). jtan = Math:tan(jrad). jcot = Math:atan(jrad). jsum = jsum + 1.
which is working from internal procedures, functions, or OO methods, with arguments/parameters either 4GL or Java-style, input, output and output-mode combinations. This converts to something like:
- if the vars are Java types:
jrad_1.assign(divide(multiply(new decimal(jdeg_1), java.lang.Math.PI), 180)); jsin_1.assign(java.lang.Math.sin(jrad_1.ref())); jcos_1.assign(java.lang.Math.cos(jrad_1.ref())); jtan_1.assign(java.lang.Math.tan(jrad_1.ref())); jcot_1.assign(java.lang.Math.atan(jrad_1.ref())); jsum_1.assign(plus(new decimal(jsum_1), 1));
- for 4GL types:
prad_1.assign(divide(multiply(pdeg_1, java.lang.Math.PI), 180)); psin_1.assign(java.lang.Math.sin(jobject.toJava(double.class, prad_1).ref())); pcos_1.assign(java.lang.Math.cos(jobject.toJava(double.class, prad_1).ref())); ptan_1.assign(java.lang.Math.tan(jobject.toJava(double.class, prad_1).ref())); pcot_1.assign(java.lang.Math.atan(jobject.toJava(double.class, prad_1).ref())); psum_1.assign(plus(psum_1, 1));
Known Issues and Next Steps
can be considered fixed. What I haven't touched:
- array/extent values
- testing for
byte[]
,java.util.Date
andjava.sql.Timestamp
#23 Updated by Greg Shah over 4 years ago
This looks very good. Check in the sample to testcases/uast/
when you are ready.
#24 Updated by Constantin Asofiei over 4 years ago
The FWD changes are in 4069a rev 11387
The test is in rev 1950.
#25 Updated by Constantin Asofiei over 4 years ago
There is a regression related to jobject.fromJava
in 4069a. Working on a fix.
#26 Updated by Constantin Asofiei over 4 years ago
I have a fix for this, will commit after regression testing.
#27 Updated by Constantin Asofiei over 4 years ago
Fixed in 4069a rev 11428.
#28 Updated by Greg Shah over 4 years ago
Task branch 4069a has been merged to trunk as revision 11339. This is the majority of the necessary support. There is one known issue which will be worked next.
#29 Updated by Greg Shah over 4 years ago
- % Done changed from 0 to 70
#30 Updated by Greg Shah over 4 years ago
At this time, the 4GL catch block is incompatible with Java's checked exceptions. This is due to the fact that we are handling these as delegated processing inside the BlockManager
and there is no try/catch in the converted Java code. I'm working on this right now.
#31 Updated by Greg Shah over 4 years ago
A testcase like this:
def var i as int. repeat: i = i / 9000. end. catch e as Progress.Lang.AppError: message "caught 1". end. catch e as Progress.Lang.Error: message "caught 2". end.
Results in this Java code:
... public class SimpleCatch3 { /** * External procedure (converted to Java from the 4GL source code * in oo/simple_catch_3.p). */ public void execute() { integer i = UndoableFactory.integer(); externalProcedure(SimpleCatch3.this, new Block((Body) () -> { repeat("loopLabel0", new Block((Body) () -> { i.assign(divide(i, 9000)); })); })); } }
Notice that the catch blocks are missing in Java. If I remove the REPEAT block, the two catchError()
instances are emitted as you would expect.
I think the problem is in convert/control_flow.rules
:
<rule>type == prog.kw_catch and parent.type == prog.statement <rule>copy.getAncestor(3).type == prog.inner_block <action>controlid = getReferenceNoteLong(copy.getAncestor(3).id, "controlid")</action> </rule> <action> lastid = createPeerAst(java.static_method_call, "catchError", controlid) </action> ...
Any catch block directly inside any top level block will not be parented at a prog.inner_block
. This means that controlid
will have whatever value it already has. If there are no nested blocks, then this is not a problem because the creation of the top level block in Java will leave a valid controlid
. But as soon as you put a nested block there (not a simple DO, but something with properties) then the controlid
will no longer be a valid value and we probably try to attach the catchError()
calls to a non-existent init()
method of the nested block. I think if the nested block had an init()
section, then the catchError()
would be attached there.
Constantin: What do you think?
I plan to fix this as part of my work. This seems like a common case. It must be affecting real code in current projects.
#32 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
Any catch block directly inside any top level block will not be parented at a
prog.inner_block
. This means thatcontrolid
will have whatever value it already has. If there are no nested blocks, then this is not a problem because the creation of the top level block in Java will leave a validcontrolid
. But as soon as you put a nested block there (not a simple DO, but something with properties) then thecontrolid
will no longer be a valid value and we probably try to attach thecatchError()
calls to a non-existentinit()
method of the nested block. I think if the nested block had aninit()
section, then thecatchError()
would be attached there.Constantin: What do you think?
Yes, I missed to test this case. The controlid
now is leaked from whatever other inner-block was previously processed; this needs to be for the top-level block.
#33 Updated by Greg Shah over 4 years ago
In BlockManager.topLevelBlock()
, it seems like this code should be aware of BLOCK-LEVEL/ROUTINE-LEVEL ON ERROR UNDO, THROW.
:
catch (ErrorConditionException err) { wa.tm.notifyCondition(err); wa.tm.honorError(err); wa.tm.rollback("methodScope"); wa.tm.triggerRetry("methodScope"); }
Also, I think this TODO in BlockManager.processCondition()
is probably also an issue:
case THROW: if (ce instanceof LegacyErrorException) { throw (LegacyErrorException) ce; } else { // TODO: build a SysError? } break;
The SysError
may only be needed in the case of BLOCK-LEVEL ON ERROR UNDO, THROW.
(but not ROUTINE-LEVEL ON ERROR UNDO, THROW.
) is specified, it is not clear to me.
Constantin: Please provide your thoughts.
#34 Updated by Greg Shah over 4 years ago
Our current parsing for catch blocks creates an INNER_BLOCK
child of the KW_CATCH
. This results in the following Java:
catchError(com.goldencode.p2j.oo.lang.LegacyError.class, e2 -> { blockLabel6: { message("external proc 2"); } });
Notice the blockLabel6
and the extra curly braces. I don't see any reason for these. I plan to remove the INNER_BLOCK
node and clean up the result.
Constantin: Do you see any flaw in my logic?
#35 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
Constantin: Do you see any flaw in my logic?
No, I can't see any way to jump to the catch block in 4GL.
#36 Updated by Greg Shah over 4 years ago
I just committed revision 11467 to branch 4335a (which is derived from 3809e revision 11466). This revision provides the following features/fixes:
- Fixes a latent bug which did not emit
CATCH
blocks properly for top-level blocks which had nested inner blocks (see #3867-31). - Removes the extra/unnecessary block emitted in
CATCH
blocks (see #3867-34). - Adds the ability to reference a fully qualified Java classname directly as a type (e.g.
DEF VAR hmap AS java.util.HashMap.
) or in aNEW
. No correspondingUSING ... FROM JAVA
is needed for this. - Supports
CATCH
blocks that reference Java exceptions (checked or unchecked) such that the code will emit a proper try/catch into the business logic so thatjavac
can be satisfied for any checked exceptions.
In regard to the new checked exceptions support, it works like this:
1. We implement all Java exceptions the same way. This means that unchecked exceptions (anything deriving from Error
or RuntimeException
) will emit the same way as any checked exception (e.g. IOException
). In both cases, we emit a try
and catch
into the Java code. In the catch
block, we emit the block as a lambda that is passed to BlockManager.processCatch()
. The block manager does not catch the exception directly. The business logic catches it and then calls to processCatch()
inside the catch
block itself.
2. The 4GL exceptions are emitted as before (although without the extra/unnecessary label plus braces). This means it is still delegated to the block manager using BlockManager.catchError()
. The try
/catch
is in the BlockManager
in this case and the lambda is passed to the BM in the init()
of the containing Block
instance.
3. It is perfectly valid to include catch blocks for Java and 4GL exceptions. The conversion emits each according to the type as described above.
To illustrate the results, this 4GL code:
using java.lang.* from java. def var i as int. function f returns int (): message "whatever". catch e as java.lang.RuntimeException: message "user-defined function". end. end. /* CATCH block may only be associated with an undoable block. (14140) */ /* do: run bogus. catch e as Progress.Lang.AppError: message "simple do". end. end. */ do transaction: run bogus. catch e as java.lang.RuntimeException: message "transaction do". end. end. repeat: i = i / 9000. leave. catch e as java.lang.RuntimeException: message "repeat". end. end. on f10 anywhere do: message "whatever". catch e as java.lang.RuntimeException: message "UI trigger". end. end. /* CATCH block may only be associated with an undoable block. (14140) */ /* update i editing: readkey. apply lastkey. catch e as Progress.Lang.AppError: message "editing block". end. end. */ catch e as java.lang.RuntimeException: message "external proc". end. catch e2 as Progress.Lang.Error: message "external proc 2". end. procedure bogus: message "whatever". catch e as java.lang.RuntimeException: message "internal proc". end. end.
will generate this Java code:
package com.goldencode.testcases.oo; import com.goldencode.p2j.util.*; import java.lang.*; import com.goldencode.p2j.ui.*; import static com.goldencode.p2j.util.BlockManager.*; import static com.goldencode.p2j.util.InternalEntry.Type; import static com.goldencode.p2j.util.MathOps.*; import static com.goldencode.p2j.ui.LogicalTerminal.*; /** * Business logic (converted to Java from the 4GL source code * in oo/catch_blocks_in_many_variations_with_direct_java.p). */ public class CatchBlocksInManyVariationsWithDirectJava { /** * External procedure (converted to Java from the 4GL source code * in oo/catch_blocks_in_many_variations_with_direct_java.p). */ public void execute() { integer i = UndoableFactory.integer(); externalProcedure(CatchBlocksInManyVariationsWithDirectJava.this, new Block((Init) () -> { catchError(com.goldencode.p2j.oo.lang.LegacyError.class, e2 -> { message("external proc 2"); }); }, (Body) () -> { try { doBlock(TransactionType.FULL, "blockLabel0", new Block((Body) () -> { try { ControlFlowOps.invoke("bogus"); } catch (java.lang.RuntimeException e) { processCatch(() -> { message("transaction do"); }); } })); repeat("loopLabel0", new Block((Body) () -> { try { i.assign(divide(i, 9000)); leave("loopLabel0"); } catch (java.lang.RuntimeException e_1) { processCatch(() -> { message("repeat"); }); } })); registerTrigger(new EventList("f10", true), CatchBlocksInManyVariationsWithDirectJava.this, () -> new Trigger() { public void pre() { } public void init() { } public void body() { try { message("whatever"); } catch (java.lang.RuntimeException e_2) { processCatch(() -> { message("UI trigger"); /* CATCH block may only be associated with an undoable block. (14140) */ /* update i editing: readkey. apply lastkey. catch e as Progress.Lang.AppError: message "editing block". end. end. */ }); } } }); } catch (java.lang.RuntimeException e_2) { processCatch(() -> { message("external proc"); }); } })); } @LegacySignature(type = Type.FUNCTION, name = "f") public integer f() { return function(this, "f", integer.class, new Block((Body) () -> { try { message("whatever"); } catch (java.lang.RuntimeException e) { processCatch(() -> { message("user-defined function"); /* CATCH block may only be associated with an undoable block. (14140) */ /* do: run bogus. catch e as Progress.Lang.AppError: message "simple do". end. end. */ }); } })); } @LegacySignature(type = Type.PROCEDURE, name = "bogus") public void bogus() { internalProcedure(new Block((Body) () -> { try { message("whatever"); } catch (java.lang.RuntimeException e_3) { processCatch(() -> { message("internal proc"); }); } })); } }
I've checked in several examples in testcases/uast/oo/
. The example above is oo/catch_blocks_in_many_variations_with_direct_java
.
#37 Updated by Greg Shah over 4 years ago
I believe that we are at a good "first pass" level of support here. A future improvement would be to support inheriting 4GL classes from Java classes, but I think that will be out of scope for now.
Constantin/Hynek: Do you know of any other critical bugs or missing features before we can accept this level as a working first version?
#38 Updated by Hynek Cihlar over 4 years ago
Greg Shah wrote:
Constantin/Hynek: Do you know of any other critical bugs or missing features before we can accept this level as a working first version?
One problem I had was with referencing enums, importing the enum class with using
didn't work. I had to fully qualify the enum class.
#39 Updated by Constantin Asofiei over 4 years ago
Greg Shah wrote:
I believe that we are at a good "first pass" level of support here. A future improvement would be to support inheriting 4GL classes from Java classes, but I think that will be out of scope for now.
Constantin/Hynek: Do you know of any other critical bugs or missing features before we can accept this level as a working first version?
I didn't test much, except what I already fixed in 3809e (related to null
values). So I'm OK with this first version.
#40 Updated by Greg Shah over 4 years ago
Hynek Cihlar wrote:
Greg Shah wrote:
Constantin/Hynek: Do you know of any other critical bugs or missing features before we can accept this level as a working first version?
One problem I had was with referencing enums, importing the enum class with
using
didn't work. I had to fully qualify the enum class.
I looked into this case. I think you are talking about ladder-safety-gen.p
where it references g:setColor(java.awt.Color:RED);
? When you use Color:RED
, it cannot parse because Color
will lex as KW_COLOR
which is reserved. When setColor
is being processed, the parser looks ahead at LA(2)
to check that it is LPARENS
and at LA(3)
to see if it can be the first token of an expr
. KW_COLOR
can never appear as the first token of an expr
so the code in downstream_chained_reference
cannot match with the method_call
rule and fails.
I hesitate to make changes for this case because such changes could cause serious parsing problems/compatibility issues with the existing 4GL OO code. If I think of a way that is safe, I will look into it, but for now I don't see a safe fix.
Currently we will have the limitation that classnames and member names which are reserved keywords might need to be qualified to work as a standalone reference or as the leftmost reference (the "anchor") of a chain of object invocations.
#41 Updated by Greg Shah over 4 years ago
- Status changed from WIP to Test
- % Done changed from 70 to 100
#42 Updated by Hynek Cihlar over 4 years ago
Greg Shah wrote:
I looked into this case. I think you are talking about
ladder-safety-gen.p
where it referencesg:setColor(java.awt.Color:RED);
?
Yes, this was the case.
#43 Updated by Greg Shah about 4 years ago
- Status changed from Test to Closed
#44 Updated by Greg Shah about 4 years ago
Task branch 4335a was merged to trunk as revision 11345.
#45 Updated by Greg Shah about 4 years ago
- Related to Bug #4620: Direct Java conversion fails for arrays of objects added