6819_20221223_6129b_14341.patch
new/src/com/goldencode/p2j/persist/RecordBuffer.java 2022-12-18 12:29:40 +0000 | ||
---|---|---|
1249 | 1249 |
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel |
1250 | 1250 |
** stack of this data for THIS-PROCEDURE. Refs #6826 |
1251 | 1251 |
** OM 20221103 New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache. |
1252 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
1253 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
1252 | 1254 |
*/ |
1253 | 1255 | |
1254 | 1256 |
/* |
... | ... | |
11719 | 11721 |
* purposes. |
11720 | 11722 |
*/ |
11721 | 11723 |
private final class Handler |
11722 |
implements InvocationHandler |
|
11724 |
implements LambdaInvocationHandler
|
|
11723 | 11725 |
{ |
11724 | 11726 |
/** Exception thrown during validation of no-undo temp-tables, which must be deferred */ |
11725 | 11727 |
private ValidationException deferredValidationError = null; |
... | ... | |
11739 | 11741 |
* The target proxy object. |
11740 | 11742 |
* @param method |
11741 | 11743 |
* The method to be executed. |
11744 |
* @param f |
|
11745 |
* A function to invoke as target, when specified. |
|
11742 | 11746 |
* @param args |
11743 | 11747 |
* The method arguments. |
11744 | 11748 |
* |
... | ... | |
11747 | 11751 |
* @throws Throwable |
11748 | 11752 |
* For various reasons, like illegal access to fields, illegal arguments or target. |
11749 | 11753 |
*/ |
11750 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
11754 |
@Override |
|
11755 |
public Object invoke(Object proxy, |
|
11756 |
final Method method, |
|
11757 |
BiFunction<Object, Object[], Object> f, |
|
11758 |
Object[] args) |
|
11751 | 11759 |
throws Throwable |
11752 | 11760 |
{ |
11753 | 11761 |
Object result = null; |
... | ... | |
11763 | 11771 |
Class<?> declClass = method.getDeclaringClass(); |
11764 | 11772 |
if (declClass.equals(BufferReference.class) || declClass.equals(Object.class)) |
11765 | 11773 |
{ |
11766 |
return Utils.invoke(method, RecordBuffer.this, args); |
|
11774 |
return Utils.invoke(method, f, RecordBuffer.this, args);
|
|
11767 | 11775 |
} |
11768 | 11776 |
|
11769 | 11777 |
// TODO: use something more reliable (but efficient) here to determine whether this is a setter |
... | ... | |
11777 | 11785 |
// to ensure all validation and flushing is tracked properly |
11778 | 11786 |
if (creatingBuffer != null && currentRecord != null && isSetter) |
11779 | 11787 |
{ |
11788 |
// this can't receive the lambdas, as they are statically linked with the this buffer's |
|
11789 |
// proxy types (a ClassCastException may be raised if the instance passed as first argument at |
|
11790 |
// the lambda is not of the expected type). |
|
11780 | 11791 |
return creatingBuffer.handler.invoke(proxy, method, args); |
11781 | 11792 |
} |
11782 | 11793 |
|
... | ... | |
11795 | 11806 |
// if a temporary record is set, use it to back the invocation |
11796 | 11807 |
if (tempRecord != null) |
11797 | 11808 |
{ |
11798 |
return Utils.invoke(method, tempRecord, args); |
|
11809 |
return Utils.invoke(method, f, tempRecord, args);
|
|
11799 | 11810 |
} |
11800 | 11811 |
|
11801 | 11812 |
if (isSetter) |
... | ... | |
11988 | 11999 |
} |
11989 | 12000 |
|
11990 | 12001 |
// invoke the method on the concrete DMO implementation |
11991 |
result = Utils.invoke(method, currentRecord, args); |
|
12002 |
result = Utils.invoke(method, f, currentRecord, args);
|
|
11992 | 12003 |
|
11993 | 12004 |
if (writeSnapshot != null && !currentRecord.checkState(DmoState.CHANGED)) |
11994 | 12005 |
{ |
... | ... | |
12120 | 12131 |
if (!ok) |
12121 | 12132 |
{ |
12122 | 12133 |
// the assign trigger vetoed: rolling back the change |
12123 |
Utils.invoke(method, currentRecord, oldVal); |
|
12134 |
Utils.invoke(method, f, currentRecord, oldVal);
|
|
12124 | 12135 |
processChange = false; |
12125 | 12136 |
} |
12126 | 12137 |
} |
... | ... | |
12795 | 12806 |
* {@link RecordBuffer} proxies. |
12796 | 12807 |
*/ |
12797 | 12808 |
private static class ArgumentBuffer |
12798 |
implements InvocationHandler |
|
12809 |
implements LambdaInvocationHandler
|
|
12799 | 12810 |
{ |
12800 | 12811 |
/** The backing buffer of this proxy, which should handle the majority of the methods */ |
12801 | 12812 |
private BufferImpl buffer; |
... | ... | |
12885 | 12896 |
* imply the DMO alias or buffer name, which will be handled here. |
12886 | 12897 |
*/ |
12887 | 12898 |
@Override |
12888 |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable |
|
12899 |
public Object invoke(Object proxy, |
|
12900 |
final Method method, |
|
12901 |
BiFunction<Object, Object[], Object> f, |
|
12902 |
Object[] args) |
|
12903 |
throws Throwable |
|
12889 | 12904 |
{ |
12890 | 12905 |
try |
12891 | 12906 |
{ |
... | ... | |
12906 | 12921 |
if (method.getName().equals("overrideName") || |
12907 | 12922 |
method.getName().equals("restoreName")) |
12908 | 12923 |
{ |
12909 |
return Utils.invoke(method, buffer, args); |
|
12924 |
return Utils.invoke(method, f, buffer, args);
|
|
12910 | 12925 |
} |
12911 | 12926 |
|
12912 | 12927 |
boolean overridden = buffer.overrideName(bufferName); |
... | ... | |
12914 | 12929 |
|
12915 | 12930 |
try |
12916 | 12931 |
{ |
12917 |
invokeReturn = Utils.invoke(method, buffer, args); |
|
12932 |
invokeReturn = Utils.invoke(method, f, buffer, args);
|
|
12918 | 12933 |
} |
12919 | 12934 |
finally |
12920 | 12935 |
{ |
... | ... | |
12944 | 12959 |
if (method.getName().equals("overrideDMOAlias") || |
12945 | 12960 |
method.getName().equals("restoreDMOAlias")) |
12946 | 12961 |
{ |
12947 |
return Utils.invoke(method, backBuffer, args); |
|
12962 |
return Utils.invoke(method, f, backBuffer, args);
|
|
12948 | 12963 |
} |
12949 | 12964 |
|
12950 | 12965 |
boolean overridden = backBuffer.overrideDMOAlias(dmoAlias); |
... | ... | |
12952 | 12967 |
|
12953 | 12968 |
try |
12954 | 12969 |
{ |
12955 |
invokeReturn = Utils.invoke(method, backBuffer, args); |
|
12970 |
invokeReturn = Utils.invoke(method, f, backBuffer, args);
|
|
12956 | 12971 |
} |
12957 | 12972 |
finally |
12958 | 12973 |
{ |
new/src/com/goldencode/p2j/persist/StaticDataSet.java 2022-12-18 12:29:52 +0000 | ||
---|---|---|
15 | 15 |
** OM 20211011 Improved debugging support for proxy objects. |
16 | 16 |
** CA 20220707 Used a static 'doNotProxy' field, instead of creating the array each time. |
17 | 17 |
** GES 20210501 Upgraded table-handle parameter processing to match API changes. |
18 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda expressions |
|
19 |
** to perform a direct call of the target, and avoid reflection. |
|
18 | 20 |
*/ |
19 | 21 | |
20 | 22 |
/* |
... | ... | |
77 | 79 |
import com.goldencode.proxy.*; |
78 | 80 |
import java.lang.reflect.*; |
79 | 81 |
import java.util.*; |
82 |
import java.util.function.*; |
|
80 | 83 | |
81 | 84 | |
82 | 85 |
/** |
... | ... | |
448 | 451 |
* {@code DataSet} or will throw errors. |
449 | 452 |
*/ |
450 | 453 |
static class Handler |
451 |
implements InvocationHandler |
|
454 |
implements LambdaInvocationHandler
|
|
452 | 455 |
{ |
453 | 456 |
/** |
454 | 457 |
* Processes a method invocation on a proxy instance and returns the result. This method |
... | ... | |
462 | 465 |
* proxy instance. The declaring class of the {@code Method} object will be the |
463 | 466 |
* interface that the method was declared in, which may be a superinterface of the |
464 | 467 |
* proxy interface that the proxy class inherits the method through. |
468 |
* @param f |
|
469 |
* A function to invoke as target, when specified. |
|
465 | 470 |
* @param args |
466 | 471 |
* an array of objects containing the values of the arguments passed in the method |
467 | 472 |
* invocation on the proxy instance, or {@code null} if interface method takes no |
... | ... | |
475 | 480 |
* @see UndeclaredThrowableException |
476 | 481 |
*/ |
477 | 482 |
@Override |
478 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
483 |
public Object invoke(Object proxy, |
|
484 |
final Method method, |
|
485 |
BiFunction<Object, Object[], Object> f, |
|
486 |
Object[] args) |
|
479 | 487 |
throws Throwable |
480 | 488 |
{ |
481 | 489 |
StaticDataSet dsp = (StaticDataSet) proxy; |
482 | 490 |
if (dsp.bound != null) |
483 | 491 |
{ |
484 |
return Utils.invoke(method, dsp.bound, args); |
|
492 |
return Utils.invoke(method, f, dsp.bound, args);
|
|
485 | 493 |
} |
486 | 494 |
|
487 | 495 |
String[] errParam = new String[1]; |
new/src/com/goldencode/p2j/persist/TemporaryBuffer.java 2022-12-18 12:30:10 +0000 | ||
---|---|---|
628 | 628 |
** IAS 20221103 Fixed support for a binded table parameter. |
629 | 629 |
** CA 20221206 If a REFERENCE-ONLY table is already bound to the same target table, do not |
630 | 630 |
** re-bind. |
631 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
632 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
631 | 633 |
*/ |
632 | 634 | |
633 | 635 |
/* |
... | ... | |
708 | 710 |
import com.goldencode.p2j.util.*; |
709 | 711 |
import com.goldencode.p2j.util.ErrorManager; |
710 | 712 |
import com.goldencode.p2j.util.LogHelper; |
711 |
import com.goldencode.proxy.ProxyFactory;
|
|
713 |
import com.goldencode.proxy.*;
|
|
712 | 714 |
import com.goldencode.util.*; |
713 | 715 | |
714 | 716 |
/** |
... | ... | |
8870 | 8872 |
* The handler to switch between references, when binding a definition table to another one. |
8871 | 8873 |
*/ |
8872 | 8874 |
private static class ReferenceProxy |
8873 |
implements InvocationHandler |
|
8875 |
implements LambdaInvocationHandler
|
|
8874 | 8876 |
{ |
8875 | 8877 |
/** The default properties - PK and any other properties in {@link TempRecord}. */ |
8876 | 8878 |
private static final Map<String, String> defaultProps; |
... | ... | |
9051 | 9053 |
* </ul> |
9052 | 9054 |
*/ |
9053 | 9055 |
@Override |
9054 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
9056 |
public Object invoke(Object proxy, |
|
9057 |
Method method, |
|
9058 |
BiFunction<Object, Object[], Object> f, |
|
9059 |
Object[] args) |
|
9055 | 9060 |
throws Throwable |
9056 | 9061 |
{ |
9057 | 9062 |
Object instance = (bound == null ? def : bound); |
... | ... | |
9074 | 9079 |
declClass.equals(Object.class) || |
9075 | 9080 |
HandleResource.class.isAssignableFrom(declClass)) |
9076 | 9081 |
{ |
9077 |
return Utils.invoke(method, instance, args); |
|
9082 |
return Utils.invoke(method, f, instance, args);
|
|
9078 | 9083 |
} |
9079 | 9084 |
|
9080 | 9085 |
if (bound != null && boundBuffer.getDMOInterface() != defBuffer.getDMOInterface()) |
... | ... | |
9094 | 9099 |
|
9095 | 9100 |
Method mbound = (setter ? boundSetterByProp.get(boundProperty) |
9096 | 9101 |
: boundGetterByProp.get(boundProperty)); |
9097 |
|
|
9102 | ||
9103 |
// instance has switched |
|
9104 |
f = null; |
|
9098 | 9105 |
method = mbound; |
9099 | 9106 |
} |
9100 | 9107 |
} |
... | ... | |
9107 | 9114 |
method.setAccessible(true); |
9108 | 9115 |
} |
9109 | 9116 |
|
9110 |
return Utils.invoke(method, instance, args); |
|
9117 |
return Utils.invoke(method, f, instance, args);
|
|
9111 | 9118 |
} |
9112 | 9119 |
catch (Throwable t) |
9113 | 9120 |
{ |
new/src/com/goldencode/p2j/util/ControlFlowOps.java 2022-12-20 11:06:30 +0000 | ||
---|---|---|
273 | 273 |
** - for typed FUNCTION calls, convert the returned value to the expected type. |
274 | 274 |
** - fixed caching issues when the call does not complete due to errors or for |
275 | 275 |
** recursive calls. |
276 |
** CA 20221220 Use lambdas to execute all public methods in an external program. |
|
276 | 277 |
*/ |
277 | 278 | |
278 | 279 |
/* |
... | ... | |
957 | 958 |
caller.callerInstance = reference; |
958 | 959 |
caller.wa = wa; |
959 | 960 |
caller.mthd = lastInvoke.method; |
961 |
caller.lambda = lastInvoke.lambda; |
|
960 | 962 |
caller.methodName = lastInvoke.method.getName(); |
961 | 963 |
caller.ie = null; |
962 | 964 |
caller.iename = cfg.getTarget(); |
... | ... | |
1003 | 1005 | |
1004 | 1006 |
try |
1005 | 1007 |
{ |
1006 |
BaseDataType res = (BaseDataType) lastInvoke.method.invoke(reference, args); |
|
1008 |
BaseDataType res = (BaseDataType) (lastInvoke.lambda == null |
|
1009 |
? lastInvoke.method.invoke(reference, args) |
|
1010 |
: lastInvoke.lambda.apply(reference, args)); |
|
1007 | 1011 |
res = checkInvokeResult(res, iename, cfg.isFunction(), cfg.isDynamicFunction()); |
1008 | 1012 |
|
1009 | 1013 |
if (cls != null && cfg.isFunction()) |
... | ... | |
8539 | 8543 |
boolean externalProcedure = caller.getPhandle() == null; |
8540 | 8544 |
Object instance = externalProcedure ? caller.getClassName() : caller.getCallerInstance(); |
8541 | 8545 |
Method mthd = externalProcedure ? null : caller.mthd; |
8546 |
BiFunction<Object, Object[], Object> lambda = externalProcedure ? null : caller.lambda; |
|
8542 | 8547 |
|
8543 | 8548 |
if (args != null && args.length > 0) |
8544 | 8549 |
{ |
... | ... | |
8556 | 8561 |
eventProcedureName, |
8557 | 8562 |
eventProcedureContext, |
8558 | 8563 |
instance, |
8559 |
mthd, |
|
8564 |
mthd, |
|
8565 |
lambda, |
|
8560 | 8566 |
argumentTypes); |
8561 | 8567 |
|
8562 | 8568 |
Map<InvokeConfig, LastInvokeDetails> thisCallSite = |
... | ... | |
8602 | 8608 |
private final Method method; |
8603 | 8609 |
|
8604 | 8610 |
/** |
8611 |
* When not-null, a lambda target to execute directly the call, without relying on the {@link #method}. |
|
8612 |
*/ |
|
8613 |
private final BiFunction<Object, Object[], Object> lambda; |
|
8614 |
|
|
8615 |
/** |
|
8605 | 8616 |
* Create a new instance. |
8606 | 8617 |
* |
8607 | 8618 |
* @param legacyName |
... | ... | |
8621 | 8632 |
* {@link #method} is being invoked. |
8622 | 8633 |
* @param method |
8623 | 8634 |
* The Java method to invoke. |
8635 |
* @param lambda |
|
8636 |
* When not-null, a lambda target to execute directly the call, without relying on the |
|
8637 |
* {@link #method}. |
|
8624 | 8638 |
* @param argumentTypes |
8625 | 8639 |
* The argument types for this call. |
8626 | 8640 |
*/ |
... | ... | |
8632 | 8646 |
Object eventProcedureContext, |
8633 | 8647 |
Object reference, |
8634 | 8648 |
Method method, |
8649 |
BiFunction<Object, Object[], Object> lambda, |
|
8635 | 8650 |
Class<?>[] argumentTypes) |
8636 | 8651 |
{ |
8637 | 8652 |
this.legacyName = legacyName; |
... | ... | |
8646 | 8661 |
|
8647 | 8662 |
this.reference = (reference == null ? null : new WeakReference<>(reference)); |
8648 | 8663 |
this.method = method; |
8664 |
this.lambda = lambda; |
|
8649 | 8665 |
} |
8650 | 8666 |
|
8651 | 8667 |
/** |
... | ... | |
9166 | 9182 |
protected WorkArea wa; |
9167 | 9183 |
|
9168 | 9184 |
/** The {@link Method} instance for this internal entry. */ |
9169 |
private Method mthd = null; |
|
9170 |
|
|
9171 |
/** The ReflectASM access point to this method. */ |
|
9172 |
private MethodAccess access = null; |
|
9173 |
|
|
9174 |
/** The method index in the MethodAccess instance. */ |
|
9175 |
private int index = -1; |
|
9185 |
protected Method mthd = null; |
|
9186 |
|
|
9187 |
protected BiFunction<Object, Object[], Object> lambda = null; |
|
9188 |
|
|
9189 |
/** Already resolved internal entry. */ |
|
9190 |
protected InternalEntry ie = null; |
|
9176 | 9191 |
|
9177 | 9192 |
/** The handle associated with the {@link #callerInstance}. */ |
9178 | 9193 |
private handle phandle; |
... | ... | |
9180 | 9195 |
/** Worker which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call. */ |
9181 | 9196 |
private Runnable pushWorker; |
9182 | 9197 |
|
9183 |
/** Already resolved internal entry (used when invoking OO methods). */ |
|
9184 |
private InternalEntry ie = null; |
|
9185 |
|
|
9186 | 9198 |
/** |
9187 | 9199 |
* Basic c'tor. |
9188 | 9200 |
* |
... | ... | |
9331 | 9343 |
pushWorker.run(); |
9332 | 9344 |
wa.bm.markPendingDynCall(); |
9333 | 9345 |
|
9334 |
if (access != null)
|
|
9346 |
if (lambda != null)
|
|
9335 | 9347 |
{ |
9336 |
return access.invoke(instance, index, param);
|
|
9348 |
return lambda.apply(instance, param);
|
|
9337 | 9349 |
} |
9338 | 9350 |
else |
9339 | 9351 |
{ |
... | ... | |
9538 | 9550 |
if (value.method != null) |
9539 | 9551 |
{ |
9540 | 9552 |
caller.mthd = value.method; |
9541 |
caller.access = value.access; |
|
9542 |
caller.index = value.index; |
|
9553 |
caller.lambda = value.lambda; |
|
9543 | 9554 |
} |
9544 | 9555 |
|
9545 | 9556 |
return value.returnValue; |
... | ... | |
9552 | 9563 |
method = cls.getMethod(caller.methodName, signature); |
9553 | 9564 |
rv = ArgValidationErrors.NO_ERROR; |
9554 | 9565 |
|
9555 |
value = putCache(key, method, rv, true); |
|
9566 |
value = putCache(key, method, null, rv, true);
|
|
9556 | 9567 | |
9557 | 9568 |
caller.mthd = value.method; |
9558 |
caller.access = value.access; |
|
9559 |
caller.index = value.index; |
|
9569 |
caller.lambda = value.lambda; |
|
9560 | 9570 |
|
9561 | 9571 |
return rv; |
9562 | 9572 |
} |
... | ... | |
9571 | 9581 |
method = cls.getDeclaredMethod(caller.methodName, signature); |
9572 | 9582 |
rv = ArgValidationErrors.NO_ERROR; |
9573 | 9583 |
|
9574 |
value = putCache(key, method, rv, true); |
|
9584 |
value = putCache(key, method, null, rv, true);
|
|
9575 | 9585 |
|
9576 | 9586 |
caller.mthd = value.method; |
9577 |
caller.access = value.access; |
|
9578 |
caller.index = value.index; |
|
9587 |
caller.lambda = value.lambda; |
|
9579 | 9588 | |
9580 | 9589 |
return rv; |
9581 | 9590 |
} |
... | ... | |
9777 | 9786 |
extentInfo[0] = parExtent ? paramExtent : 0; |
9778 | 9787 |
|
9779 | 9788 |
// cache failed result as well, so we don't need to repeat validation |
9780 |
putCache(key, null, rv, true); |
|
9789 |
putCache(key, null, null, rv, true);
|
|
9781 | 9790 |
|
9782 | 9791 |
return rv; |
9783 | 9792 |
} |
... | ... | |
10197 | 10206 |
|
10198 | 10207 |
if (value == null) |
10199 | 10208 |
{ |
10200 |
value = putCache(key, m, rv, false); |
|
10209 |
value = putCache(key, m, m.equals(caller.mthd) ? caller.lambda : null, rv, false);
|
|
10201 | 10210 |
} |
10202 | 10211 |
|
10203 | 10212 |
caller.mthd = value.method; |
10204 |
caller.access = value.access; |
|
10205 |
caller.index = value.index; |
|
10213 |
caller.lambda = value.lambda; |
|
10206 | 10214 |
|
10207 | 10215 |
return rv; |
10208 | 10216 |
} |
... | ... | |
10215 | 10223 |
: ArgValidationErrors.INCORRECT_TYPES; |
10216 | 10224 |
|
10217 | 10225 |
// cache failed result as well, so we don't need to repeat validation |
10218 |
putCache(key, null, rv, true); |
|
10226 |
putCache(key, null, null, rv, true);
|
|
10219 | 10227 |
|
10220 | 10228 |
return rv; |
10221 | 10229 |
} |
... | ... | |
10351 | 10359 |
* @param method |
10352 | 10360 |
* Method found matching a given name and signature, or {@code null} if no |
10353 | 10361 |
* match was found. |
10362 |
* @param lambda |
|
10363 |
* When not-null, a lambda target to execute directly the call, without relying on the method. |
|
10354 | 10364 |
* @param returnValue |
10355 | 10365 |
* Enumerated return value for {@link #valid(boolean, String, int[], Object...)} |
10356 | 10366 |
* method. |
... | ... | |
10362 | 10372 |
* @return the created cache. |
10363 | 10373 |
*/ |
10364 | 10374 |
private static CacheValue putCache(CacheKey key, |
10365 |
Method method, |
|
10366 |
ArgValidationErrors returnValue, |
|
10367 |
boolean exactMatch) |
|
10375 |
Method method, |
|
10376 |
BiFunction<Object, Object[], Object> lambda, |
|
10377 |
ArgValidationErrors returnValue, |
|
10378 |
boolean exactMatch) |
|
10368 | 10379 |
{ |
10369 |
CacheValue value = new CacheValue(method, returnValue, exactMatch); |
|
10380 |
CacheValue value = new CacheValue(method, lambda, returnValue, exactMatch);
|
|
10370 | 10381 |
|
10371 | 10382 |
synchronized (cache) |
10372 | 10383 |
{ |
... | ... | |
10496 | 10507 |
/** Method, if any, else <code>null</code> if none was found */ |
10497 | 10508 |
final Method method; |
10498 | 10509 | |
10499 |
/** The ReflectASM access point to this method. */
|
|
10500 |
final MethodAccess access;
|
|
10501 |
|
|
10502 |
/** The method index in the MethodAccess instance. */
|
|
10503 |
final int index;
|
|
10510 |
/** |
|
10511 |
* When not-null, a lambda target to execute directly the call, without relying on the
|
|
10512 |
* {@link #method}. |
|
10513 |
*/ |
|
10514 |
final BiFunction<Object, Object[], Object> lambda;
|
|
10504 | 10515 |
|
10505 | 10516 |
/** |
10506 | 10517 |
* Enumerated return value for {@link |
... | ... | |
10516 | 10527 |
* |
10517 | 10528 |
* @param method |
10518 | 10529 |
* Method, if any, else <code>null</code> if none was found. |
10530 |
* @param lambda |
|
10531 |
* When not-null, a lambda target to execute directly the call, without relying on the |
|
10532 |
* {@link #method}. |
|
10519 | 10533 |
* @param returnValue |
10520 | 10534 |
* Enumerated return value for {@link |
10521 | 10535 |
* InternalEntryCaller#valid(boolean, String, int[], Object...)}. |
... | ... | |
10523 | 10537 |
* Flag indicating whether signature of caller's parameters exactly matches |
10524 | 10538 |
* method. |
10525 | 10539 |
*/ |
10526 |
CacheValue(Method method, ArgValidationErrors returnValue, boolean exactMatch) |
|
10540 |
CacheValue(Method method, BiFunction<Object, Object[], Object> lambda, ArgValidationErrors returnValue, boolean exactMatch)
|
|
10527 | 10541 |
{ |
10528 | 10542 |
this.method = method; |
10529 |
if (method != null) |
|
10530 |
{ |
|
10531 |
if (!Modifier.isPrivate(method.getModifiers())) |
|
10532 |
{ |
|
10533 |
this.access = MethodAccess.get(method.getDeclaringClass()); |
|
10534 |
this.index = access.getIndex(method.getName(), method.getParameterTypes()); |
|
10535 |
} |
|
10536 |
else |
|
10537 |
{ |
|
10538 |
this.access = null; |
|
10539 |
this.index = -1; |
|
10540 |
} |
|
10541 |
} |
|
10542 |
else |
|
10543 |
{ |
|
10544 |
this.access = null; |
|
10545 |
this.index = -1; |
|
10546 |
} |
|
10543 |
this.lambda = lambda; |
|
10547 | 10544 |
this.returnValue = returnValue; |
10548 | 10545 |
this.exactMatch = exactMatch; |
10549 | 10546 |
} |
... | ... | |
10747 | 10744 |
* |
10748 | 10745 |
* @param wa |
10749 | 10746 |
* The context-local state in {@link WorkArea}. |
10747 |
* @param name |
|
10748 |
* The external program name. |
|
10750 | 10749 |
* @param className |
10751 | 10750 |
* The class name associated with the external program. |
10752 | 10751 |
* @param persistent |
... | ... | |
10755 | 10754 |
* @throws Exception |
10756 | 10755 |
* If the class can't be resolved. |
10757 | 10756 |
*/ |
10758 |
public ExternalProcedureCaller(WorkArea wa, String className, boolean persistent) |
|
10757 |
public ExternalProcedureCaller(WorkArea wa, String name, String className, boolean persistent)
|
|
10759 | 10758 |
throws Exception |
10760 | 10759 |
{ |
10761 | 10760 |
super(wa); |
10761 | ||
10762 |
this.ie = SourceNameMapper.getInternalEntry(name, null, false); |
|
10763 |
this.mthd = (ie == null ? null : ie.getMethod()); |
|
10764 |
this.lambda = (ie == null ? null : ie.getLambda()); |
|
10762 | 10765 |
|
10763 | 10766 |
this.methodName = "execute"; |
10764 | 10767 |
this.className = className; |
... | ... | |
10965 | 10968 |
wa.currentInvoke = null; |
10966 | 10969 |
res = new MapToNotFound(wa, mapTo); |
10967 | 10970 |
} |
10968 |
|
|
10971 |
else |
|
10972 |
{ |
|
10973 |
res.mthd = ie.getMethod(); |
|
10974 |
res.lambda = ie.getLambda(); |
|
10975 |
} |
|
10976 | ||
10969 | 10977 |
res.ie = ie; |
10970 | 10978 |
return res; |
10971 | 10979 |
} |
... | ... | |
10983 | 10991 |
|
10984 | 10992 |
caller = new InternalEntryCaller(wa, hcaller, ie.jname, ieName); |
10985 | 10993 |
caller.ie = ie; |
10994 |
caller.mthd = ie.getMethod(); |
|
10995 |
caller.lambda = ie.getLambda(); |
|
10986 | 10996 |
} |
10987 | 10997 |
/* |
10988 | 10998 |
else if (!superCall) |
... | ... | |
11163 | 11173 |
// name to any errors generated during instantiation (i.e. shared vars error msgs |
11164 | 11174 |
// need to know the procedure name where they are defined, if an error happens) |
11165 | 11175 |
wa.pm.setInstantingExternalProgram(name); |
11166 |
caller = new ExternalProcedureCaller(wa, className, persistent); |
|
11176 |
caller = new ExternalProcedureCaller(wa, name, className, persistent);
|
|
11167 | 11177 |
} |
11168 | 11178 |
catch (Throwable t) |
11169 | 11179 |
{ |
new/src/com/goldencode/p2j/util/InternalEntry.java 2022-12-20 11:06:40 +0000 | ||
---|---|---|
31 | 31 |
** 016 ME 20200810 Added qualified name support in signature processing. |
32 | 32 |
** CA 20210218 Fixed signature processing for qualified return type. |
33 | 33 |
** CA 20220909 Fixed TABLE-HANDLE, DATASET, DATASET-HANDLE parameters reported by GET-SIGNATURE. |
34 |
** CA 20221220 Use lambdas to execute all public methods in an external program. Fixed synchronization |
|
35 |
** in getMethod() and getLambda(), when calling SourceNameMapper.buildLegacyProgram. |
|
34 | 36 |
*/ |
35 | 37 | |
36 | 38 |
/* |
... | ... | |
90 | 92 | |
91 | 93 |
import java.lang.reflect.*; |
92 | 94 |
import java.util.*; |
95 |
import java.util.function.*; |
|
93 | 96 | |
94 | 97 |
import com.goldencode.p2j.util.SourceNameMapper.ExternalProgram; |
95 | 98 | |
... | ... | |
269 | 272 |
* Flag indicating if this is a IN SUPER internal entry (when set to <code>true</code>). |
270 | 273 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
271 | 274 |
*/ |
272 |
private Boolean isSuper = null;
|
|
275 |
private boolean isSuper = false;
|
|
273 | 276 |
|
274 | 277 |
/** |
275 | 278 |
* Flag indicating if this is a IN handle internal entry (when set to <code>true</code>). |
276 | 279 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
277 | 280 |
*/ |
278 |
private Boolean inHandle = null;
|
|
281 |
private boolean inHandle = false;
|
|
279 | 282 |
|
280 | 283 |
/** |
281 | 284 |
* Flag indicating if this is a PRIVATE internal entry (when set to <code>true</code>). |
282 | 285 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
283 | 286 |
*/ |
284 |
private Boolean isPrivate = null;
|
|
287 |
private boolean isPrivate = false;
|
|
285 | 288 |
|
286 | 289 |
/** The return type's extent. */ |
287 | 290 |
private int extent = SourceNameMapper.NO_EXTENT; |
... | ... | |
294 | 297 |
*/ |
295 | 298 |
private Method method = null; |
296 | 299 |
|
300 |
/** When not-null, represents a lambda function to execute directly the target. */ |
|
301 |
private BiFunction<Object, Object[], Object> lambda = null; |
|
302 |
|
|
297 | 303 |
/** The OS library name, if this is a native method. */ |
298 | 304 |
private String libname = null; |
299 | 305 |
|
... | ... | |
452 | 458 |
*/ |
453 | 459 |
public Method getMethod() |
454 | 460 |
{ |
455 |
if (method == null && extprog != null && extprog.ooname == null) |
|
461 |
if (method == null && extprog != null && extprog.buildLegacy && extprog.ooname == null)
|
|
456 | 462 |
{ |
457 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
458 |
|
|
459 |
// in case the Java method is not needed for this entry (is a in-handle or in-super), avoid multiple |
|
460 |
// lookups. |
|
461 |
extprog = null; |
|
463 |
synchronized (extprog) |
|
464 |
{ |
|
465 |
if (extprog.buildLegacy) |
|
466 |
{ |
|
467 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
468 |
extprog.buildLegacy = false; |
|
469 |
} |
|
470 |
} |
|
462 | 471 |
} |
463 | 472 |
|
464 | 473 |
return method; |
465 | 474 |
} |
466 | 475 |
|
467 | 476 |
/** |
477 |
* Get the lambda target to be executed, see {@link #lambda}. |
|
478 |
* |
|
479 |
* @return See above. |
|
480 |
*/ |
|
481 |
public BiFunction<Object, Object[], Object> getLambda() |
|
482 |
{ |
|
483 |
if (method == null && extprog != null && extprog.buildLegacy && extprog.ooname == null) |
|
484 |
{ |
|
485 |
synchronized (extprog) |
|
486 |
{ |
|
487 |
if (extprog.buildLegacy) |
|
488 |
{ |
|
489 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
490 |
extprog.buildLegacy = false; |
|
491 |
} |
|
492 |
} |
|
493 |
} |
|
494 |
|
|
495 |
return lambda; |
|
496 |
} |
|
497 |
|
|
498 |
/** |
|
468 | 499 |
* Get the value of the given attribute. If the attribute is null or |
469 | 500 |
* is not set, return <code>null</code>. |
470 | 501 |
* |
... | ... | |
581 | 612 |
} |
582 | 613 |
|
583 | 614 |
/** |
615 |
* Set the target lambda for this internal entry to the specified one. |
|
616 |
* |
|
617 |
* @param lambda |
|
618 |
* The target lambda. |
|
619 |
*/ |
|
620 |
void setLambda(BiFunction<Object, Object[], Object> lambda) |
|
621 |
{ |
|
622 |
this.lambda = lambda; |
|
623 |
} |
|
624 |
|
|
625 |
/** |
|
584 | 626 |
* Set the external program for this entry. |
585 | 627 |
* |
586 | 628 |
* @param extprog |
new/src/com/goldencode/p2j/util/SourceNameMapper.java 2022-12-20 11:06:48 +0000 | ||
---|---|---|
150 | 150 |
** initialized at server startup, the 'initMappingData' calls were removed from the |
151 | 151 |
** APIs. |
152 | 152 |
** CA 20220609 Fixed a regression in destructor processing. |
153 |
** CA 20221220 Use lambdas to execute all public methods in an external program. |
|
153 | 154 |
*/ |
154 | 155 | |
155 | 156 |
/* |
... | ... | |
223 | 224 |
import com.goldencode.p2j.oo.lang.*; |
224 | 225 |
import com.goldencode.p2j.rest.*; |
225 | 226 |
import com.goldencode.p2j.soap.*; |
227 |
import com.goldencode.proxy.*; |
|
226 | 228 |
import com.goldencode.p2j.security.*; |
227 | 229 | |
228 | 230 |
/** |
... | ... | |
2935 | 2937 |
throw new RuntimeException(e); |
2936 | 2938 |
} |
2937 | 2939 | |
2940 |
Map<Method, BiFunction<Object, Object[], Object>> lambdas = LambdaFactory.buildLambdas(cls); |
|
2941 |
|
|
2938 | 2942 |
Method[] methods = cls.getDeclaredMethods(); |
2939 | 2943 |
for (Method mthd : methods) |
2940 | 2944 |
{ |
... | ... | |
2960 | 2964 |
if (ie != null) |
2961 | 2965 |
{ |
2962 | 2966 |
ie.setMethod(mthd); |
2967 |
|
|
2968 |
if (lambdas != null) |
|
2969 |
{ |
|
2970 |
BiFunction<Object, Object[], Object> lambda = lambdas.get(mthd); |
|
2971 |
ie.setLambda(lambda); |
|
2972 |
} |
|
2963 | 2973 |
} |
2964 | 2974 |
} |
2965 | 2975 |
} |
... | ... | |
3438 | 3448 |
InternalEntry main = null; |
3439 | 3449 |
|
3440 | 3450 |
/** |
3451 |
* Flag indicating if {@link SourceNameMapper#buildLegacyClass(Class)} needs to be called when |
|
3452 |
* {@link InternalEntry#getMethod()} or {@link InternalEntry#getLambda()} are accessed. |
|
3453 |
*/ |
|
3454 |
boolean buildLegacy = true; |
|
3455 |
|
|
3456 |
/** |
|
3441 | 3457 |
* Basic c'tor. |
3442 | 3458 |
* |
3443 | 3459 |
* @param pname |
new/src/com/goldencode/p2j/util/Utils.java 2022-12-18 12:38:06 +0000 | ||
---|---|---|
184 | 184 |
** RFB 20220504 Minor javadoc fixes. |
185 | 185 |
** SBI 20220504 Fixed toChar(int, String) to return -1 if key code is not represented by |
186 | 186 |
** the given char set. |
187 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
188 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
187 | 189 |
*/ |
188 | 190 | |
189 | 191 |
/* |
... | ... | |
1601 | 1603 |
* More, the maps are identity-keyed, for fast access. |
1602 | 1604 |
* |
1603 | 1605 |
* @param method |
1606 |
* The method to execute, only if the function or consumer are both null. |
|
1607 |
* @param f |
|
1608 |
* When not-null, invoke this function and return its value. |
|
1609 |
* @param instance |
|
1610 |
* The instance on which the call will be made; can be <code>null</code> for static methods. |
|
1611 |
* @param args |
|
1612 |
* The method's arguments. |
|
1613 |
* |
|
1614 |
* @return The method's return value. |
|
1615 |
* |
|
1616 |
* @throws IllegalAccessException |
|
1617 |
* @throws IllegalArgumentException |
|
1618 |
* @throws InvocationTargetException |
|
1619 |
*/ |
|
1620 |
public static Object invoke(Method method, |
|
1621 |
BiFunction<Object, Object[], Object> f, |
|
1622 |
Object instance, |
|
1623 |
Object... args) |
|
1624 |
throws IllegalAccessException, |
|
1625 |
IllegalArgumentException, |
|
1626 |
InvocationTargetException |
|
1627 |
{ |
|
1628 |
if (f != null) |
|
1629 |
{ |
|
1630 |
return f.apply(instance, args); |
|
1631 |
} |
|
1632 |
else |
|
1633 |
{ |
|
1634 |
return invoke(method, instance, args); |
|
1635 |
} |
|
1636 |
} |
|
1637 |
|
|
1638 |
/** |
|
1639 |
* Invoke the specified {@link Method} using ReflectASM instead of standard reflection. |
|
1640 |
* <p> |
|
1641 |
* ReflectASM related data is cached in {@link #METHOD_DATA}, thread-local, to avoid the concurrent access. |
|
1642 |
* More, the maps are identity-keyed, for fast access. |
|
1643 |
* |
|
1644 |
* @param method |
|
1604 | 1645 |
* The method to execute. |
1605 | 1646 |
* @param instance |
1606 | 1647 |
* The instance on which the call will be made; can be <code>null</code> for static methods. |
new/src/com/goldencode/proxy/DefaultHandlerPlugin.java 2022-12-12 15:05:06 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : DefaultHandlerPlugin.java |
|
3 |
** Abstract : |
|
4 |
** |
|
5 |
** Copyright (c) 2022, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------- |
|
8 |
** 001 CA 20221212 Created initial version. |
|
9 |
*/ |
|
10 |
/* |
|
11 |
** This program is free software: you can redistribute it and/or modify |
|
12 |
** it under the terms of the GNU Affero General Public License as |
|
13 |
** published by the Free Software Foundation, either version 3 of the |
|
14 |
** License, or (at your option) any later version. |
|
15 |
** |
|
16 |
** This program is distributed in the hope that it will be useful, |
|
17 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
19 |
** GNU Affero General Public License for more details. |
|
20 |
** |
|
21 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
22 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
23 |
** |
|
24 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
25 |
** |
|
26 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
27 |
** terms apply to the works covered under the License. These additional terms |
|
28 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
29 |
** Affero GPL version 3 and may not be removed by you. |
|
30 |
** |
|
31 |
** 0. Attribution Requirement. |
|
32 |
** |
|
33 |
** You must preserve all legal notices or author attributions in the covered |
|
34 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
35 |
** work. You may not remove from the covered work any author or developer |
|
36 |
** credit already included within the covered work. |
|
37 |
** |
|
38 |
** 1. No License To Use Trademarks. |
|
39 |
** |
|
40 |
** This license does not grant any license or rights to use the trademarks |
|
41 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
42 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
43 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
44 |
** publicity purposes without written authorization. |
|
45 |
** |
|
46 |
** 2. No Misrepresentation of Affiliation. |
|
47 |
** |
|
48 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
49 |
** |
|
50 |
** You may not represent yourself for publicity purposes as associated with |
|
51 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
52 |
** the covered work, without written authorization. |
|
53 |
** |
|
54 |
** 3. No Misrepresentation of Source or Origin. |
|
55 |
** |
|
56 |
** You may not represent the covered work as solely your work. All modified |
|
57 |
** versions of the covered work must be marked in a reasonable way to make it |
|
58 |
** clear that the modified work is not originating from Golden Code Development |
|
59 |
** Corporation or FWD. All modified versions must contain the notices of |
|
60 |
** attribution required in this license. |
|
61 |
*/ |
|
62 | ||
63 |
package com.goldencode.proxy; |
|
64 | ||
65 |
import java.lang.reflect.*; |
|
66 | ||
67 |
import org.objectweb.asm.*; |
|
68 | ||
69 |
import com.goldencode.asm.AsmUtils; |
|
70 | ||
71 |
/** |
|
72 |
* Assembler for the invocation arguments emitted for the {@link InvocationHandler#invoke} method. |
|
73 |
*/ |
|
74 |
class DefaultHandlerPlugin |
|
75 |
implements InvocationHandlerPlugin |
|
76 |
{ |
|
77 |
/** |
|
78 |
* Get the handler's class. |
|
79 |
* |
|
80 |
* @return {@link InvocationHandler} type. |
|
81 |
*/ |
|
82 |
@Override |
|
83 |
public Class<? extends InvocationHandler> getHandlerClass() |
|
84 |
{ |
|
85 |
return InvocationHandler.class; |
|
86 |
} |
|
87 | ||
88 |
/** |
|
89 |
* Get the signature of the 'invoke' method for the {@link InvocationHandler}. |
|
90 |
*/ |
|
91 |
@Override |
|
92 |
public String getInvokeSignature() |
|
93 |
{ |
|
94 |
return "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"; |
|
95 |
} |
|
96 | ||
97 |
/** |
|
98 |
* Create the arguments related to invoking the target of this proxied call. This will emit a {@link Method} |
|
99 |
* reference from within the 'ma' array at the specified 'methodIndex'. |
|
100 |
* |
|
101 |
* @param classWriter |
|
102 |
* Class writer for proxy class. |
|
103 |
* @param mv |
|
104 |
* Method visitor for this method. |
|
105 |
* @param proxyName |
|
106 |
* Internal form of the proxy class' name. |
|
107 |
* @param method |
|
108 |
* Method for which this proxy is being implemented. |
|
109 |
* @param methodIndex |
|
110 |
* Index of this method within the array of methods used by the invocation handler. |
|
111 |
*/ |
|
112 |
@Override |
|
113 |
public void createInvocationArgs(ClassWriter classWriter, |
|
114 |
MethodVisitor mv, |
|
115 |
String proxyName, |
|
116 |
Method method, |
|
117 |
int methodIndex) |
|
118 |
{ |
|
119 |
// load target method from array of methods in static field "ma" |
|
120 |
mv.visitFieldInsn(ProxyAssembler.GETSTATIC, |
|
121 |
proxyName, |
|
122 |
"ma", |
|
123 |
"[Ljava/lang/reflect/Method;"); |
|
124 |
|
|
125 |
// target method is at index "index" in the "ma" array |
|
126 |
AsmUtils.pushInt(mv, methodIndex); |
|
127 |
mv.visitInsn(Opcodes.AALOAD); |
|
128 |
} |
|
129 |
} |
new/src/com/goldencode/proxy/InvocationHandlerPlugin.java 2022-12-12 15:27:10 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : InvocationHandlerPlugin.java |
|
3 |
** Abstract : |
|
4 |
** |
|
5 |
** Copyright (c) 2022, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------- |
|
8 |
** 001 CA 20221212 Created initial version. |
|
9 |
*/ |
|
10 |
/* |
|
11 |
** This program is free software: you can redistribute it and/or modify |
|
12 |
** it under the terms of the GNU Affero General Public License as |
|
13 |
** published by the Free Software Foundation, either version 3 of the |
|
14 |
** License, or (at your option) any later version. |
|
15 |
** |
|
16 |
** This program is distributed in the hope that it will be useful, |
|
17 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
19 |
** GNU Affero General Public License for more details. |
|
20 |
** |
|
21 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
22 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
23 |
** |
|
24 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
25 |
** |
|
26 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
27 |
** terms apply to the works covered under the License. These additional terms |
|
28 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
29 |
** Affero GPL version 3 and may not be removed by you. |
|
30 |
** |
|
31 |
** 0. Attribution Requirement. |
|
32 |
** |
|
33 |
** You must preserve all legal notices or author attributions in the covered |
|
34 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
35 |
** work. You may not remove from the covered work any author or developer |
|
36 |
** credit already included within the covered work. |
|
37 |
** |
|
38 |
** 1. No License To Use Trademarks. |
|
39 |
** |
|
40 |
** This license does not grant any license or rights to use the trademarks |
|
41 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
42 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
43 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
44 |
** publicity purposes without written authorization. |
|
45 |
** |
|
46 |
** 2. No Misrepresentation of Affiliation. |
|
47 |
** |
|
48 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
49 |
** |
|
50 |
** You may not represent yourself for publicity purposes as associated with |
|
51 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
52 |
** the covered work, without written authorization. |
|
53 |
** |
|
54 |
** 3. No Misrepresentation of Source or Origin. |
|
55 |
** |
|
56 |
** You may not represent the covered work as solely your work. All modified |
|
57 |
** versions of the covered work must be marked in a reasonable way to make it |
|
58 |
** clear that the modified work is not originating from Golden Code Development |
|
59 |
** Corporation or FWD. All modified versions must contain the notices of |
|
60 |
** attribution required in this license. |
|
61 |
*/ |
|
62 | ||
63 |
package com.goldencode.proxy; |
|
64 | ||
65 |
import java.lang.reflect.*; |
|
66 | ||
67 |
import org.objectweb.asm.*; |
|
68 | ||
69 |
/** |
|
70 |
* Assembler for the invocation arguments emitted for the 'invoke' method at the handler. |
|
71 |
*/ |
|
72 |
public interface InvocationHandlerPlugin |
|
73 |
{ |
|
74 |
/** |
|
75 |
* Get the handler's class. |
|
76 |
* |
|
77 |
* @return A sub-class of {@link InvocationHandler}. |
|
78 |
*/ |
|
79 |
public Class<? extends InvocationHandler> getHandlerClass(); |
|
80 | ||
81 |
/** |
|
82 |
* Get the signature of the 'invoke' method for the {@link #getHandlerClass() handler type}. |
|
83 |
*/ |
|
84 |
public String getInvokeSignature(); |
|
85 | ||
86 |
/** |
|
87 |
* Create the arguments related to invoking the target of this proxied call. This will emit at least the |
|
88 |
* {@link Method} reference. |
|
89 |
* |
|
90 |
* @param classWriter |
|
91 |
* Class writer for proxy class. |
|
92 |
* @param mv |
|
93 |
* Method visitor for this method. |
|
94 |
* @param proxyName |
|
95 |
* Internal form of the proxy class' name. |
|
96 |
* @param method |
|
97 |
* Method for which this proxy is being implemented. |
|
98 |
* @param methodIndex |
|
99 |
* Index of this method within the array of methods used by the invocation handler. |
|
100 |
*/ |
|
101 |
public void createInvocationArgs(ClassWriter classWriter, |
|
102 |
MethodVisitor mv, |
|
103 |
String proxyName, |
|
104 |
Method method, |
|
105 |
int methodIndex); |
|
106 |
} |
new/src/com/goldencode/proxy/LambdaAssembler.java 2022-12-20 11:07:18 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : LambdaAssembler.java |
|
3 |
** Abstract : Assembles a dynamic class which defines lambdas for all reachable public methods in a certain |
|
4 |
** type. |
|
5 |
** |
|
6 |
** Copyright (c) 2022, Golden Code Development Corporation. |
|
7 |
** |
|
8 |
** -#- -I- --Date-- --------------------------------Description---------------------------------- |
|
9 |
** 001 CA 20221220 Created initial version. |
|
10 |
*/ |
|
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.proxy; |
|
65 | ||
66 |
import java.lang.reflect.*; |
|
67 |
import java.util.*; |
|
68 |
import java.util.function.*; |
|
69 | ||
70 |
import org.objectweb.asm.*; |
|
71 |
import org.objectweb.asm.Type; |
|
72 | ||
73 |
import com.goldencode.asm.*; |
|
74 | ||
75 |
/** |
|
76 |
* A class which assembles dynamic types defining lambda functions for all public, non-static, methods of |
|
77 |
* that type (and its super-classes and super-interfaceS). This is done using the ASM bytecode engineering |
|
78 |
* library, v5.0.4 (http://asm.ow2.org). As a package private class, it is intended to be invoked only by the |
|
79 |
* {@link LambadFactory}, which exposes a public API for the definition of new proxy objects. |
|
80 |
* <p> |
|
81 |
* This mechanism is used to replace Java reflection (via {@link Method#invoke(Object, Object...)} or |
|
82 |
* ReflectASM, to perform dynamic calls. |
|
83 |
* <p> |
|
84 |
* The following pseudo-code representation is intended to convey the general structure of the generated type. |
|
85 |
* However, please note that no intermediate source code representation is produced. |
|
86 |
* <p> |
|
87 |
* <pre> |
|
88 |
* public final class $__[type]Lambdas |
|
89 |
* { |
|
90 |
* // the mapping of Method instances to the BiFunction lambda |
|
91 |
* private static final HashMap lambdas = new HashMap(); |
|
92 |
* |
|
93 |
* // get the 'lambdas' map. |
|
94 |
* public static Map getLambdas() |
|
95 |
* { |
|
96 |
* return lambdas; |
|
97 |
* } |
|
98 |
* |
|
99 |
* static |
|
100 |
* { |
|
101 |
* try |
|
102 |
* { |
|
103 |
* Method m = [type].class.getDeclaredMethod("[method]", [arg types...]); |
|
104 |
* lambdas.put(m, (ref, args) -> { return (([type]) ref).[method](args[0], args[1], ...); }); |
|
105 |
* ... |
|
106 |
* // each method in the given type |
|
107 |
* ... |
|
108 |
* } |
|
109 |
* catch (Throwable t) |
|
110 |
* { |
|
111 |
* throw new RuntimeException(t); |
|
112 |
* } |
|
113 |
* } |
|
114 |
* } |
|
115 |
* </pre> |
|
116 |
*/ |
|
117 |
class LambdaAssembler |
|
118 |
implements Opcodes |
|
119 |
{ |
|
120 |
/** The name of the static map holding the lambdas. */ |
|
121 |
private static final String FIELD_LAMBDAS = "lambdas"; |
|
122 |
|
|
123 |
/** Object used to create class fields, methods, etc. */ |
|
124 |
private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | |
|
125 |
ClassWriter.COMPUTE_MAXS); |
|
126 |
|
|
127 |
/** The name of the type where the lambdas will be defined. */ |
|
128 |
private final String typeName; |
|
129 |
|
|
130 |
/** The methods which require lambda definitions. */ |
|
131 |
private final HashSet<Method> methods; |
|
132 | ||
133 |
/** A counter for the number of lambdas defined in this type. */ |
|
134 |
private int lambdaNum = 0; |
|
135 |
|
|
136 |
/** |
|
137 |
* Constructor. |
|
138 |
* |
|
139 |
* @param typeName |
|
140 |
* The name of the type where the lambdas will be defined. |
|
141 |
* @param methods |
|
142 |
* The methods which require lambda definitions. |
|
143 |
*/ |
|
144 |
LambdaAssembler(String typeName, HashSet<Method> methods) |
|
145 |
{ |
|
146 |
this.typeName = AsmUtils.commonToInternalTypeName(typeName); |
|
147 |
this.methods = methods; |
|
148 |
} |
|
149 | ||
150 |
/** |
|
151 |
* Assemble the class in accordance with the Java class file format as specified in <u>The Java Virtual |
|
152 |
* Machine Specification, Second Edition</u> by Tim Lindholm and Frank Yellin. |
|
153 |
* <p> |
|
154 |
* See the class description above for details regarding the content of the assembled class. |
|
155 |
* |
|
156 |
* @return Byte array containing the class definition. |
|
157 |
*/ |
|
158 |
public byte[] assembleClass() |
|
159 |
{ |
|
160 |
// create the dynamic class |
|
161 |
classWriter.visit(V1_8, |
|
162 |
ACC_PUBLIC | ACC_FINAL | ACC_SUPER, |
|
163 |
typeName, |
|
164 |
null, |
|
165 |
"java/lang/Object", |
|
166 |
null); |
|
167 |
|
|
168 |
// add a class member field for the lambda map |
|
169 |
classWriter.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, |
|
170 |
FIELD_LAMBDAS, |
|
171 |
"Ljava/util/HashMap;", |
|
172 |
null, |
|
173 |
null); |
|
174 |
|
|
175 |
assembleLambdaGetter(); |
|
176 |
|
|
177 |
assembleLambdas(); |
|
178 |
|
|
179 |
// complete the assembly |
|
180 |
classWriter.visitEnd(); |
|
181 | ||
182 |
// get the byte array that represents the class file |
|
183 |
byte[] code = classWriter.toByteArray(); |
|
184 | ||
185 |
return code; |
|
186 |
} |
|
187 | ||
188 |
/** |
|
189 |
* Emit the bytecode for the lambda call. |
|
190 |
* |
|
191 |
* @param typeName |
|
192 |
* The type where this lambda is defined. |
|
193 |
* @param method |
|
194 |
* The target method. |
|
195 |
* @param lambdaName |
|
196 |
* The name of this synthetic lambda method. |
|
197 |
* @param classWriter |
|
198 |
* Class writer for proxy class. |
|
199 |
* @param mv |
|
200 |
* Method writer, where this lambda is being invoked. |
|
201 |
*/ |
|
202 |
public static void emitLambdaCall(String typeName, |
|
203 |
Method method, |
|
204 |
String lambdaName, |
|
205 |
ClassWriter classWriter, |
|
206 |
MethodVisitor mv) |
|
207 |
{ |
|
208 |
String retType = "Ljava/lang/Object;"; |
|
209 |
|
|
210 |
Object[] mthdArgs = |
|
211 |
{ |
|
212 |
Type.getMethodType("(Ljava/lang/Object;Ljava/lang/Object;)" + retType), |
|
213 |
new Handle(H_INVOKESTATIC, |
|
214 |
typeName, |
|
215 |
lambdaName, |
|
216 |
"(Ljava/lang/Object;[Ljava/lang/Object;)" + retType), |
|
217 |
Type.getMethodType("(Ljava/lang/Object;[Ljava/lang/Object;)" + retType) |
|
218 |
}; |
|
219 |
|
|
220 |
Handle h = new Handle(H_INVOKESTATIC, |
|
221 |
"java/lang/invoke/LambdaMetafactory", |
|
222 |
"metafactory", |
|
223 |
"(Ljava/lang/invoke/MethodHandles$Lookup;" + |
|
224 |
"Ljava/lang/String;Ljava/lang/invoke/MethodType;" + |
|
225 |
"Ljava/lang/invoke/MethodType;" + |
|
226 |
"Ljava/lang/invoke/MethodHandle;" + |
|
227 |
"Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" |
|
228 |
); |
|
229 | ||
230 |
mv.visitInvokeDynamicInsn("apply", "()Ljava/util/function/BiFunction;", h, mthdArgs); |
|
231 |
|
|
232 |
Class<?> clazz = method.getDeclaringClass(); |
|
233 |
if (clazz.isAnonymousClass()) |
|
234 |
{ |
|
235 |
classWriter.visitInnerClass(AsmUtils.commonToInternalTypeName(clazz.getName()), |
|
236 |
AsmUtils.commonToInternalTypeName(clazz.getDeclaringClass().getName()), |
|
237 |
clazz.getSimpleName(), |
|
238 |
clazz.getModifiers()); |
|
239 |
} |
|
240 |
classWriter.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", |
|
241 |
"java/lang/invoke/MethodHandles", |
|
242 |
"Lookup", |
|
243 |
ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
|
244 |
} |
|
245 | ||
246 |
/** |
|
247 |
* Emit a the bytecode for the synthetic lambda method. This will perform the actual Java method call, |
|
248 |
* by using the first lambda argument as the reference (in case of instance methods) and emiting the |
|
249 |
* arguments from the second <code>Object[]</code> lambda argument. |
|
250 |
* <p> |
|
251 |
* Arguments for Java native type parameters are automatically unwrapped. |
|
252 |
* |
|
253 |
* @param classWriter |
|
254 |
* Class writer for proxy class. |
|
255 |
* @param lambdaName |
|
256 |
* The name of this synthetic lambda method. |
|
257 |
* @param method |
|
258 |
* Method for which this proxy is being implemented. |
|
259 |
*/ |
|
260 |
static void emitLambdaMethod(ClassWriter classWriter, String lambdaName, Method method) |
|
261 |
{ |
|
262 |
boolean isVoid = method.getReturnType() == void.class; |
|
263 |
|
|
264 |
String desc = "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"; |
|
265 |
|
|
266 |
MethodVisitor mv = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, |
|
267 |
lambdaName, |
|
268 |
desc, |
|
269 |
null, |
|
270 |
new String[0]); |
|
271 |
mv.visitCode(); |
|
272 |
|
|
273 |
int maxStack = 0; |
|
274 |
|
|
275 |
{ |
|
276 |
Class<?> clazz = method.getDeclaringClass(); |
|
277 |
String clazzDesc = AsmUtils.commonToInternalTypeName(clazz); |
|
278 |
// TODO: null instances (static methods) |
|
279 |
mv.visitVarInsn(ALOAD, 0); |
|
280 |
mv.visitTypeInsn(CHECKCAST, clazzDesc); |
|
281 |
|
|
282 |
Class<?>[] parmTypes = method.getParameterTypes(); |
|
283 |
for (int i = 0; i < parmTypes.length; i++) |
|
284 |
{ |
|
285 |
TypeInfo parmInfo = ProxyUtils.getTypeInfo(method.getParameterTypes()[i]); |
|
286 | ||
287 |
maxStack += parmInfo.getWidth(); |
|
288 |
|
|
289 |
mv.visitVarInsn(ALOAD, 1); |
|
290 |
AsmUtils.pushInt(mv, i); |
|
291 |
mv.visitInsn(AALOAD); |
|
292 |
|
|
293 |
MethodInfo unwrapInfo = parmInfo.getUnwrapInfo(); |
|
294 |
String castName = parmInfo.getCastName(); |
|
295 |
if (castName != null && !"java/lang/Object".equals(castName)) |
|
296 |
{ |
|
297 |
mv.visitTypeInsn(CHECKCAST, parmInfo.getCastName()); |
|
298 |
} |
|
299 |
|
|
300 |
if (unwrapInfo != null) |
|
301 |
{ |
|
302 |
mv.visitMethodInsn(INVOKEVIRTUAL, |
|
303 |
unwrapInfo.getClassName(), |
|
304 |
unwrapInfo.getMethodName(), |
|
305 |
unwrapInfo.getDescriptor(), |
|
306 |
false); |
|
307 |
} |
|
308 |
} |
|
309 |
desc = ProxyUtils.makeMethodDescriptor(method); |
|
310 |
boolean isStatic = Modifier.isStatic(method.getModifiers()); |
|
311 |
boolean isIFace = clazz.isInterface(); |
|
312 |
boolean isAbstract = Modifier.isAbstract(method.getModifiers()); |
|
313 |
boolean isPublic = Modifier.isPublic(method.getModifiers()); |
|
314 |
mv.visitMethodInsn(isStatic ? INVOKESTATIC : isIFace ? INVOKEINTERFACE : INVOKEVIRTUAL, |
|
315 |
clazzDesc, |
|
316 |
method.getName(), |
|
317 |
desc, |
|
318 |
isIFace); |
|
319 |
|
|
320 |
if (isVoid) |
|
321 |
{ |
|
322 |
mv.visitInsn(ACONST_NULL); |
|
323 |
} |
|
324 |
else |
|
325 |
{ |
|
326 |
TypeInfo retInfo = ProxyUtils.getTypeInfo(method.getReturnType()); |
|
327 |
MethodInfo wrapInfo = retInfo.getWrapInfo(); |
|
328 |
if (wrapInfo != null) |
|
329 |
{ |
|
330 |
mv.visitMethodInsn(INVOKESTATIC, |
|
331 |
wrapInfo.getClassName(), |
|
332 |
wrapInfo.getMethodName(), |
|
333 |
wrapInfo.getDescriptor(), |
|
334 |
false); |
|
335 |
} |
|
336 |
} |
|
337 |
|
|
338 |
mv.visitInsn(ARETURN); |
|
339 |
} |
|
340 |
|
|
341 |
mv.visitMaxs(maxStack, 2); |
|
342 |
mv.visitEnd(); |
|
343 |
} |
|
344 | ||
345 |
/** |
|
346 |
* Assemble the <code>getLambdas</code> public, static, method, which returns the {@link #FIELD_LAMBDAS} |
|
347 |
* map. |
|
348 |
*/ |
|
349 |
private void assembleLambdaGetter() |
|
350 |
{ |
|
351 |
MethodVisitor mv = |
|
352 |
classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, |
|
353 |
"getLambdas", |
|
354 |
"()Ljava/util/Map;", |
|
355 |
null, |
|
356 |
null); |
|
357 | ||
358 |
// get the current value of the "lambdas" static field |
|
359 |
mv.visitFieldInsn(GETSTATIC, |
|
360 |
typeName, |
|
361 |
FIELD_LAMBDAS, |
|
362 |
"Ljava/util/HashMap;"); |
|
363 |
|
|
364 |
mv.visitInsn(ARETURN); |
|
365 |
|
|
366 |
mv.visitMaxs(0, 0); |
|
367 |
mv.visitEnd(); |
|
368 |
} |
|
369 | ||
370 |
/** |
|
371 |
* Assemble all lambdas, to populate the 'lambdas' map. This will create a static constructor definition |
|
372 |
* where, for each {@link #methods method}, the lambda is created and put in the map. |
|
373 |
* <p> |
|
374 |
* All lambdas are a {@link BiFunction}, with <code>Object</code> as first type (the reference on which the |
|
375 |
* target method is being invoked), and a <code>Object[]</code> second type, with the arguments to be |
|
376 |
* passed to the method call. |
|
377 |
*/ |
|
378 |
private void assembleLambdas() |
|
379 |
{ |
|
380 |
// create the class static constructor |
|
381 |
MethodVisitor mv = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); |
|
382 |
|
|
383 |
Label tryLblStart = new Label(); |
|
384 |
Label tryLblEnd = new Label(); |
|
385 |
Label catchLblStart = new Label(); |
|
386 |
Label catchLblEnd = new Label(); |
|
387 |
Label tryBlock = new Label(); |
|
388 | ||
389 |
Label mlblStart = tryLblStart; |
|
390 |
Label mlblEnd = tryLblEnd; |
|
391 |
Label tlblStart = catchLblStart; |
|
392 |
Label tlblEnd = catchLblEnd; |
|
393 |
|
|
394 |
mv.visitLabel(new Label()); |
|
395 |
// initialize the 'lambdas' static field |
|
396 |
mv.visitTypeInsn(NEW, "java/util/HashMap"); |