6939_6819_lambdas_3821c_14461.patch
new/src/com/goldencode/p2j/persist/RecordBuffer.java 2022-12-21 16:46:32 +0000 | ||
---|---|---|
1243 | 1243 |
** CA 20221101 Accessing a setter while not in a TRANSACTION will raise error 7369. |
1244 | 1244 |
** TJD 20220504 Upgrade do Java 11 minor changes |
1245 | 1245 |
** OM 20221125 Added referenceOnly method. |
1246 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
1247 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
1246 | 1248 |
*/ |
1247 | 1249 | |
1248 | 1250 |
/* |
... | ... | |
11823 | 11825 |
* purposes. |
11824 | 11826 |
*/ |
11825 | 11827 |
private final class Handler |
11826 |
implements InvocationHandler |
|
11828 |
implements LambdaInvocationHandler
|
|
11827 | 11829 |
{ |
11828 | 11830 |
/** Exception thrown during validation of no-undo temp-tables, which must be deferred */ |
11829 | 11831 |
private ValidationException deferredValidationError = null; |
... | ... | |
11843 | 11845 |
* The target proxy object. |
11844 | 11846 |
* @param method |
11845 | 11847 |
* The method to be executed. |
11848 |
* @param f |
|
11849 |
* A function to invoke as target, when specified. |
|
11846 | 11850 |
* @param args |
11847 | 11851 |
* The method arguments. |
11848 | 11852 |
* |
... | ... | |
11851 | 11855 |
* @throws Throwable |
11852 | 11856 |
* For various reasons, like illegal access to fields, illegal arguments or target. |
11853 | 11857 |
*/ |
11854 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
11858 |
@Override |
|
11859 |
public Object invoke(Object proxy, |
|
11860 |
final Method method, |
|
11861 |
BiFunction<Object, Object[], Object> f, |
|
11862 |
Object[] args) |
|
11855 | 11863 |
throws Throwable |
11856 | 11864 |
{ |
11857 | 11865 |
Object result = null; |
... | ... | |
11867 | 11875 |
Class<?> declClass = method.getDeclaringClass(); |
11868 | 11876 |
if (declClass.equals(BufferReference.class) || declClass.equals(Object.class)) |
11869 | 11877 |
{ |
11870 |
return Utils.invoke(method, RecordBuffer.this, args); |
|
11878 |
return Utils.invoke(method, f, RecordBuffer.this, args);
|
|
11871 | 11879 |
} |
11872 | 11880 |
|
11873 | 11881 |
// TODO: use something more reliable (but efficient) here to determine whether this is a setter |
... | ... | |
11889 | 11897 |
// to ensure all validation and flushing is tracked properly |
11890 | 11898 |
if (creatingBuffer != null && currentRecord != null && isSetter) |
11891 | 11899 |
{ |
11900 |
// this can't receive the lambdas, as they are statically linked with the this buffer's |
|
11901 |
// proxy types (a ClassCastException may be raised if the instance passed as first argument at |
|
11902 |
// the lambda is not of the expected type). |
|
11892 | 11903 |
return creatingBuffer.handler.invoke(proxy, method, args); |
11893 | 11904 |
} |
11894 | 11905 |
|
... | ... | |
11907 | 11918 |
// if a temporary record is set, use it to back the invocation |
11908 | 11919 |
if (tempRecord != null) |
11909 | 11920 |
{ |
11910 |
return Utils.invoke(method, tempRecord, args); |
|
11921 |
return Utils.invoke(method, f, tempRecord, args);
|
|
11911 | 11922 |
} |
11912 | 11923 |
|
11913 | 11924 |
if (isSetter) |
... | ... | |
12101 | 12112 |
} |
12102 | 12113 |
|
12103 | 12114 |
// invoke the method on the concrete DMO implementation |
12104 |
result = Utils.invoke(method, currentRecord, args); |
|
12115 |
result = Utils.invoke(method, f, currentRecord, args);
|
|
12105 | 12116 |
|
12106 | 12117 |
if (writeSnapshot != null && !currentRecord.checkState(DmoState.CHANGED)) |
12107 | 12118 |
{ |
... | ... | |
12233 | 12244 |
if (!ok) |
12234 | 12245 |
{ |
12235 | 12246 |
// the assign trigger vetoed: rolling back the change |
12236 |
Utils.invoke(method, currentRecord, oldVal); |
|
12247 |
Utils.invoke(method, f, currentRecord, oldVal);
|
|
12237 | 12248 |
processChange = false; |
12238 | 12249 |
} |
12239 | 12250 |
} |
... | ... | |
12908 | 12919 |
* {@link RecordBuffer} proxies. |
12909 | 12920 |
*/ |
12910 | 12921 |
private static class ArgumentBuffer |
12911 |
implements InvocationHandler |
|
12922 |
implements LambdaInvocationHandler
|
|
12912 | 12923 |
{ |
12913 | 12924 |
/** The backing buffer of this proxy, which should handle the majority of the methods */ |
12914 | 12925 |
private BufferImpl buffer; |
... | ... | |
12998 | 13009 |
* imply the DMO alias or buffer name, which will be handled here. |
12999 | 13010 |
*/ |
13000 | 13011 |
@Override |
13001 |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable |
|
13012 |
public Object invoke(Object proxy, |
|
13013 |
final Method method, |
|
13014 |
BiFunction<Object, Object[], Object> f, |
|
13015 |
Object[] args) |
|
13016 |
throws Throwable |
|
13002 | 13017 |
{ |
13003 | 13018 |
try |
13004 | 13019 |
{ |
... | ... | |
13019 | 13034 |
if (method.getName().equals("overrideName") || |
13020 | 13035 |
method.getName().equals("restoreName")) |
13021 | 13036 |
{ |
13022 |
return Utils.invoke(method, buffer, args); |
|
13037 |
return Utils.invoke(method, f, buffer, args);
|
|
13023 | 13038 |
} |
13024 | 13039 |
|
13025 | 13040 |
boolean overridden = buffer.overrideName(bufferName); |
... | ... | |
13027 | 13042 |
|
13028 | 13043 |
try |
13029 | 13044 |
{ |
13030 |
invokeReturn = Utils.invoke(method, buffer, args); |
|
13045 |
invokeReturn = Utils.invoke(method, f, buffer, args);
|
|
13031 | 13046 |
} |
13032 | 13047 |
finally |
13033 | 13048 |
{ |
... | ... | |
13057 | 13072 |
if (method.getName().equals("overrideDMOAlias") || |
13058 | 13073 |
method.getName().equals("restoreDMOAlias")) |
13059 | 13074 |
{ |
13060 |
return Utils.invoke(method, backBuffer, args); |
|
13075 |
return Utils.invoke(method, f, backBuffer, args);
|
|
13061 | 13076 |
} |
13062 | 13077 |
|
13063 | 13078 |
boolean overridden = backBuffer.overrideDMOAlias(dmoAlias); |
... | ... | |
13065 | 13080 |
|
13066 | 13081 |
try |
13067 | 13082 |
{ |
13068 |
invokeReturn = Utils.invoke(method, backBuffer, args); |
|
13083 |
invokeReturn = Utils.invoke(method, f, backBuffer, args);
|
|
13069 | 13084 |
} |
13070 | 13085 |
finally |
13071 | 13086 |
{ |
new/src/com/goldencode/p2j/persist/StaticDataSet.java 2022-12-21 16:37:00 +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 |
** OM 20221206 Reworked REFERENCE-ONLY attribute. |
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 |
/** |
... | ... | |
444 | 447 |
* {@code DataSet} or will throw errors. |
445 | 448 |
*/ |
446 | 449 |
static class Handler |
447 |
implements InvocationHandler |
|
450 |
implements LambdaInvocationHandler
|
|
448 | 451 |
{ |
449 | 452 |
/** |
450 | 453 |
* Processes a method invocation on a proxy instance and returns the result. This method |
... | ... | |
458 | 461 |
* proxy instance. The declaring class of the {@code Method} object will be the |
459 | 462 |
* interface that the method was declared in, which may be a superinterface of the |
460 | 463 |
* proxy interface that the proxy class inherits the method through. |
464 |
* @param f |
|
465 |
* A function to invoke as target, when specified. |
|
461 | 466 |
* @param args |
462 | 467 |
* an array of objects containing the values of the arguments passed in the method |
463 | 468 |
* invocation on the proxy instance, or {@code null} if interface method takes no |
... | ... | |
471 | 476 |
* @see UndeclaredThrowableException |
472 | 477 |
*/ |
473 | 478 |
@Override |
474 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
479 |
public Object invoke(Object proxy, |
|
480 |
final Method method, |
|
481 |
BiFunction<Object, Object[], Object> f, |
|
482 |
Object[] args) |
|
475 | 483 |
throws Throwable |
476 | 484 |
{ |
477 | 485 |
StaticDataSet dsp = (StaticDataSet) proxy; |
478 | 486 |
if (dsp.bound != null) |
479 | 487 |
{ |
480 |
return Utils.invoke(method, dsp.bound, args); |
|
488 |
return Utils.invoke(method, f, dsp.bound, args);
|
|
481 | 489 |
} |
482 | 490 |
|
483 | 491 |
String[] errParam = new String[1]; |
new/src/com/goldencode/p2j/persist/TemporaryBuffer.java 2022-12-21 16:49:46 +0000 | ||
---|---|---|
591 | 591 |
** OM 20221125 Handled BINDing of REFERENCE-ONLY OUTPUT buffers. |
592 | 592 |
** AL2 20221205 Replaced AdaptiveQuery with PreselectQuery for loopDelete. |
593 | 593 |
** Use null sort instead of empty sort for PreselectQuery/ |
594 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
595 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
594 | 596 |
*/ |
595 | 597 | |
596 | 598 |
/* |
... | ... | |
670 | 672 |
import com.goldencode.p2j.util.*; |
671 | 673 |
import com.goldencode.p2j.util.ErrorManager; |
672 | 674 |
import com.goldencode.p2j.util.LogHelper; |
673 |
import com.goldencode.proxy.ProxyFactory;
|
|
675 |
import com.goldencode.proxy.*;
|
|
674 | 676 |
import com.goldencode.util.*; |
675 | 677 | |
676 | 678 |
/** |
... | ... | |
8838 | 8840 |
* The handler to switch between references, when binding a definition table to another one. |
8839 | 8841 |
*/ |
8840 | 8842 |
private static class ReferenceProxy |
8841 |
implements InvocationHandler |
|
8843 |
implements LambdaInvocationHandler
|
|
8842 | 8844 |
{ |
8843 | 8845 |
/** The default properties - PK and any other properties in {@link TempRecord}. */ |
8844 | 8846 |
private static final Map<String, String> defaultProps; |
... | ... | |
8995 | 8997 |
*/ |
8996 | 8998 |
@SuppressWarnings("deprecation") |
8997 | 8999 |
@Override |
8998 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
9000 |
public Object invoke(Object proxy, |
|
9001 |
Method method, |
|
9002 |
BiFunction<Object, Object[], Object> f, |
|
9003 |
Object[] args) |
|
8999 | 9004 |
throws Throwable |
9000 | 9005 |
{ |
9001 | 9006 |
Object instance = (bound == null ? def : bound); |
... | ... | |
9018 | 9023 |
declClass.equals(Object.class) || |
9019 | 9024 |
HandleResource.class.isAssignableFrom(declClass)) |
9020 | 9025 |
{ |
9021 |
return Utils.invoke(method, instance, args); |
|
9026 |
return Utils.invoke(method, f, instance, args);
|
|
9022 | 9027 |
} |
9023 | 9028 |
|
9024 | 9029 |
if (bound != null && boundBuffer.getDMOInterface() != defBuffer.getDMOInterface()) |
... | ... | |
9038 | 9043 |
|
9039 | 9044 |
Method mbound = (setter ? boundSetterByProp.get(boundProperty) |
9040 | 9045 |
: boundGetterByProp.get(boundProperty)); |
9041 |
|
|
9046 | ||
9047 |
// instance has switched |
|
9048 |
f = null; |
|
9042 | 9049 |
method = mbound; |
9043 | 9050 |
} |
9044 | 9051 |
} |
... | ... | |
9053 | 9060 |
method.setAccessible(true); |
9054 | 9061 |
} |
9055 | 9062 |
|
9056 |
return Utils.invoke(method, instance, args); |
|
9063 |
return Utils.invoke(method, f, instance, args);
|
|
9057 | 9064 |
} |
9058 | 9065 |
catch (Throwable t) |
9059 | 9066 |
{ |
new/src/com/goldencode/p2j/util/ControlFlowOps.java 2022-12-21 16:44:00 +0000 | ||
---|---|---|
264 | 264 |
** TW 20221123 Augmented the error feedback, which omitted the target invocation name if |
265 | 265 |
** no parameters expected. Troubleshooting went haywire after that, since |
266 | 266 |
** root causes were sought at the wrong frame in the stack (refs: #6906). |
267 |
** CA 20221220 Use lambdas to execute all public methods in an external program. |
|
267 | 268 |
*/ |
268 | 269 | |
269 | 270 |
/* |
... | ... | |
948 | 949 |
caller.callerInstance = reference; |
949 | 950 |
caller.wa = wa; |
950 | 951 |
caller.mthd = lastInvoke.method; |
952 |
caller.lambda = lastInvoke.lambda; |
|
951 | 953 |
caller.methodName = lastInvoke.method.getName(); |
952 | 954 |
caller.ie = null; |
953 | 955 |
caller.iename = cfg.getTarget(); |
... | ... | |
994 | 996 | |
995 | 997 |
try |
996 | 998 |
{ |
997 |
BaseDataType res = (BaseDataType) lastInvoke.method.invoke(reference, args); |
|
999 |
BaseDataType res = (BaseDataType) (lastInvoke.lambda == null |
|
1000 |
? lastInvoke.method.invoke(reference, args) |
|
1001 |
: lastInvoke.lambda.apply(reference, args)); |
|
998 | 1002 |
res = checkInvokeResult(res, iename, cfg.isFunction(), cfg.isDynamicFunction()); |
999 | 1003 |
|
1000 | 1004 |
if (cls != null && cfg.isFunction()) |
... | ... | |
8392 | 8396 |
boolean externalProcedure = caller.getPhandle() == null; |
8393 | 8397 |
Object instance = externalProcedure ? caller.getClassName() : caller.getCallerInstance(); |
8394 | 8398 |
Method mthd = externalProcedure ? null : caller.mthd; |
8399 |
BiFunction<Object, Object[], Object> lambda = externalProcedure ? null : caller.lambda; |
|
8395 | 8400 |
|
8396 | 8401 |
if (args != null && args.length > 0) |
8397 | 8402 |
{ |
... | ... | |
8409 | 8414 |
eventProcedureName, |
8410 | 8415 |
eventProcedureContext, |
8411 | 8416 |
instance, |
8412 |
mthd, |
|
8417 |
mthd, |
|
8418 |
lambda, |
|
8413 | 8419 |
argumentTypes); |
8414 | 8420 |
|
8415 | 8421 |
Map<InvokeConfig, LastInvokeDetails> thisCallSite = |
... | ... | |
8455 | 8461 |
private final Method method; |
8456 | 8462 |
|
8457 | 8463 |
/** |
8464 |
* When not-null, a lambda target to execute directly the call, without relying on the {@link #method}. |
|
8465 |
*/ |
|
8466 |
private final BiFunction<Object, Object[], Object> lambda; |
|
8467 |
|
|
8468 |
/** |
|
8458 | 8469 |
* Create a new instance. |
8459 | 8470 |
* |
8460 | 8471 |
* @param legacyName |
... | ... | |
8474 | 8485 |
* {@link #method} is being invoked. |
8475 | 8486 |
* @param method |
8476 | 8487 |
* The Java method to invoke. |
8488 |
* @param lambda |
|
8489 |
* When not-null, a lambda target to execute directly the call, without relying on the |
|
8490 |
* {@link #method}. |
|
8477 | 8491 |
* @param argumentTypes |
8478 | 8492 |
* The argument types for this call. |
8479 | 8493 |
*/ |
... | ... | |
8485 | 8499 |
Object eventProcedureContext, |
8486 | 8500 |
Object reference, |
8487 | 8501 |
Method method, |
8502 |
BiFunction<Object, Object[], Object> lambda, |
|
8488 | 8503 |
Class<?>[] argumentTypes) |
8489 | 8504 |
{ |
8490 | 8505 |
this.legacyName = legacyName; |
... | ... | |
8499 | 8514 |
|
8500 | 8515 |
this.reference = (reference == null ? null : new WeakReference<>(reference)); |
8501 | 8516 |
this.method = method; |
8517 |
this.lambda = lambda; |
|
8502 | 8518 |
} |
8503 | 8519 |
|
8504 | 8520 |
/** |
... | ... | |
8934 | 8950 |
protected WorkArea wa; |
8935 | 8951 |
|
8936 | 8952 |
/** The {@link Method} instance for this internal entry. */ |
8937 |
private Method mthd = null; |
|
8938 |
|
|
8939 |
/** The ReflectASM access point to this method. */ |
|
8940 |
private MethodAccess access = null; |
|
8941 |
|
|
8942 |
/** The method index in the MethodAccess instance. */ |
|
8943 |
private int index = -1; |
|
8953 |
protected Method mthd = null; |
|
8954 |
|
|
8955 |
protected BiFunction<Object, Object[], Object> lambda = null; |
|
8956 |
|
|
8957 |
/** Already resolved internal entry. */ |
|
8958 |
protected InternalEntry ie = null; |
|
8944 | 8959 |
|
8945 | 8960 |
/** The handle associated with the {@link #callerInstance}. */ |
8946 | 8961 |
private handle phandle; |
... | ... | |
8948 | 8963 |
/** Worker which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call. */ |
8949 | 8964 |
private Runnable pushWorker; |
8950 | 8965 |
|
8951 |
/** Already resolved internal entry (used when invoking OO methods). */ |
|
8952 |
private InternalEntry ie = null; |
|
8953 |
|
|
8954 | 8966 |
/** |
8955 | 8967 |
* Basic c'tor. |
8956 | 8968 |
* |
... | ... | |
9099 | 9111 |
pushWorker.run(); |
9100 | 9112 |
wa.bm.markPendingDynCall(); |
9101 | 9113 |
|
9102 |
if (access != null)
|
|
9114 |
if (lambda != null)
|
|
9103 | 9115 |
{ |
9104 |
return access.invoke(instance, index, param);
|
|
9116 |
return lambda.apply(instance, param);
|
|
9105 | 9117 |
} |
9106 | 9118 |
else |
9107 | 9119 |
{ |
... | ... | |
9306 | 9318 |
if (value.method != null) |
9307 | 9319 |
{ |
9308 | 9320 |
caller.mthd = value.method; |
9309 |
caller.access = value.access; |
|
9310 |
caller.index = value.index; |
|
9321 |
caller.lambda = value.lambda; |
|
9311 | 9322 |
} |
9312 | 9323 |
|
9313 | 9324 |
return value.returnValue; |
... | ... | |
9320 | 9331 |
method = cls.getMethod(caller.methodName, signature); |
9321 | 9332 |
rv = ArgValidationErrors.NO_ERROR; |
9322 | 9333 |
|
9323 |
value = putCache(key, method, rv, true); |
|
9334 |
value = putCache(key, method, null, rv, true);
|
|
9324 | 9335 | |
9325 | 9336 |
caller.mthd = value.method; |
9326 |
caller.access = value.access; |
|
9327 |
caller.index = value.index; |
|
9337 |
caller.lambda = value.lambda; |
|
9328 | 9338 |
|
9329 | 9339 |
return rv; |
9330 | 9340 |
} |
... | ... | |
9339 | 9349 |
method = cls.getDeclaredMethod(caller.methodName, signature); |
9340 | 9350 |
rv = ArgValidationErrors.NO_ERROR; |
9341 | 9351 |
|
9342 |
value = putCache(key, method, rv, true); |
|
9352 |
value = putCache(key, method, null, rv, true);
|
|
9343 | 9353 |
|
9344 | 9354 |
caller.mthd = value.method; |
9345 |
caller.access = value.access; |
|
9346 |
caller.index = value.index; |
|
9355 |
caller.lambda = value.lambda; |
|
9347 | 9356 | |
9348 | 9357 |
return rv; |
9349 | 9358 |
} |
... | ... | |
9541 | 9550 |
extentInfo[0] = parExtent ? paramExtent : 0; |
9542 | 9551 |
|
9543 | 9552 |
// cache failed result as well, so we don't need to repeat validation |
9544 |
putCache(key, null, rv, true); |
|
9553 |
putCache(key, null, null, rv, true);
|
|
9545 | 9554 |
|
9546 | 9555 |
return rv; |
9547 | 9556 |
} |
... | ... | |
9961 | 9970 |
|
9962 | 9971 |
if (value == null) |
9963 | 9972 |
{ |
9964 |
value = putCache(key, m, rv, false); |
|
9973 |
value = putCache(key, m, m.equals(caller.mthd) ? caller.lambda : null, rv, false);
|
|
9965 | 9974 |
} |
9966 | 9975 |
|
9967 | 9976 |
caller.mthd = value.method; |
9968 |
caller.access = value.access; |
|
9969 |
caller.index = value.index; |
|
9977 |
caller.lambda = value.lambda; |
|
9970 | 9978 |
|
9971 | 9979 |
return rv; |
9972 | 9980 |
} |
... | ... | |
9979 | 9987 |
: ArgValidationErrors.INCORRECT_TYPES; |
9980 | 9988 |
|
9981 | 9989 |
// cache failed result as well, so we don't need to repeat validation |
9982 |
putCache(key, null, rv, true); |
|
9990 |
putCache(key, null, null, rv, true);
|
|
9983 | 9991 |
|
9984 | 9992 |
return rv; |
9985 | 9993 |
} |
... | ... | |
10115 | 10123 |
* @param method |
10116 | 10124 |
* Method found matching a given name and signature, or {@code null} if no |
10117 | 10125 |
* match was found. |
10126 |
* @param lambda |
|
10127 |
* When not-null, a lambda target to execute directly the call, without relying on the method. |
|
10118 | 10128 |
* @param returnValue |
10119 | 10129 |
* Enumerated return value for {@link #valid(boolean, String, int[], Object...)} |
10120 | 10130 |
* method. |
... | ... | |
10126 | 10136 |
* @return the created cache. |
10127 | 10137 |
*/ |
10128 | 10138 |
private static CacheValue putCache(CacheKey key, |
10129 |
Method method, |
|
10130 |
ArgValidationErrors returnValue, |
|
10131 |
boolean exactMatch) |
|
10139 |
Method method, |
|
10140 |
BiFunction<Object, Object[], Object> lambda, |
|
10141 |
ArgValidationErrors returnValue, |
|
10142 |
boolean exactMatch) |
|
10132 | 10143 |
{ |
10133 |
CacheValue value = new CacheValue(method, returnValue, exactMatch); |
|
10144 |
CacheValue value = new CacheValue(method, lambda, returnValue, exactMatch);
|
|
10134 | 10145 |
|
10135 | 10146 |
synchronized (cache) |
10136 | 10147 |
{ |
... | ... | |
10260 | 10271 |
/** Method, if any, else <code>null</code> if none was found */ |
10261 | 10272 |
final Method method; |
10262 | 10273 | |
10263 |
/** The ReflectASM access point to this method. */
|
|
10264 |
final MethodAccess access;
|
|
10265 |
|
|
10266 |
/** The method index in the MethodAccess instance. */
|
|
10267 |
final int index;
|
|
10274 |
/** |
|
10275 |
* When not-null, a lambda target to execute directly the call, without relying on the
|
|
10276 |
* {@link #method}. |
|
10277 |
*/ |
|
10278 |
final BiFunction<Object, Object[], Object> lambda;
|
|
10268 | 10279 |
|
10269 | 10280 |
/** |
10270 | 10281 |
* Enumerated return value for {@link |
... | ... | |
10280 | 10291 |
* |
10281 | 10292 |
* @param method |
10282 | 10293 |
* Method, if any, else <code>null</code> if none was found. |
10294 |
* @param lambda |
|
10295 |
* When not-null, a lambda target to execute directly the call, without relying on the |
|
10296 |
* {@link #method}. |
|
10283 | 10297 |
* @param returnValue |
10284 | 10298 |
* Enumerated return value for {@link |
10285 | 10299 |
* InternalEntryCaller#valid(boolean, String, int[], Object...)}. |
... | ... | |
10287 | 10301 |
* Flag indicating whether signature of caller's parameters exactly matches |
10288 | 10302 |
* method. |
10289 | 10303 |
*/ |
10290 |
CacheValue(Method method, ArgValidationErrors returnValue, boolean exactMatch) |
|
10304 |
CacheValue(Method method, BiFunction<Object, Object[], Object> lambda, ArgValidationErrors returnValue, boolean exactMatch)
|
|
10291 | 10305 |
{ |
10292 | 10306 |
this.method = method; |
10293 |
if (method != null) |
|
10294 |
{ |
|
10295 |
if (!Modifier.isPrivate(method.getModifiers())) |
|
10296 |
{ |
|
10297 |
this.access = MethodAccess.get(method.getDeclaringClass()); |
|
10298 |
this.index = access.getIndex(method.getName(), method.getParameterTypes()); |
|
10299 |
} |
|
10300 |
else |
|
10301 |
{ |
|
10302 |
this.access = null; |
|
10303 |
this.index = -1; |
|
10304 |
} |
|
10305 |
} |
|
10306 |
else |
|
10307 |
{ |
|
10308 |
this.access = null; |
|
10309 |
this.index = -1; |
|
10310 |
} |
|
10307 |
this.lambda = lambda; |
|
10311 | 10308 |
this.returnValue = returnValue; |
10312 | 10309 |
this.exactMatch = exactMatch; |
10313 | 10310 |
} |
... | ... | |
10511 | 10508 |
* |
10512 | 10509 |
* @param wa |
10513 | 10510 |
* The context-local state in {@link WorkArea}. |
10511 |
* @param name |
|
10512 |
* The external program name. |
|
10514 | 10513 |
* @param className |
10515 | 10514 |
* The class name associated with the external program. |
10516 | 10515 |
* @param persistent |
... | ... | |
10519 | 10518 |
* @throws Exception |
10520 | 10519 |
* If the class can't be resolved. |
10521 | 10520 |
*/ |
10522 |
public ExternalProcedureCaller(WorkArea wa, String className, boolean persistent) |
|
10521 |
public ExternalProcedureCaller(WorkArea wa, String name, String className, boolean persistent)
|
|
10523 | 10522 |
throws Exception |
10524 | 10523 |
{ |
10525 | 10524 |
super(wa); |
10525 | ||
10526 |
this.ie = SourceNameMapper.getInternalEntry(name, null, false); |
|
10527 |
this.mthd = (ie == null ? null : ie.getMethod()); |
|
10528 |
this.lambda = (ie == null ? null : ie.getLambda()); |
|
10526 | 10529 |
|
10527 | 10530 |
this.methodName = "execute"; |
10528 | 10531 |
this.className = className; |
... | ... | |
10729 | 10732 |
wa.currentInvoke = null; |
10730 | 10733 |
res = new MapToNotFound(wa, mapTo); |
10731 | 10734 |
} |
10732 |
|
|
10735 |
else |
|
10736 |
{ |
|
10737 |
res.mthd = ie.getMethod(); |
|
10738 |
res.lambda = ie.getLambda(); |
|
10739 |
} |
|
10740 | ||
10733 | 10741 |
res.ie = ie; |
10734 | 10742 |
return res; |
10735 | 10743 |
} |
... | ... | |
10747 | 10755 |
|
10748 | 10756 |
caller = new InternalEntryCaller(wa, hcaller, ie.jname, ieName); |
10749 | 10757 |
caller.ie = ie; |
10758 |
caller.mthd = ie.getMethod(); |
|
10759 |
caller.lambda = ie.getLambda(); |
|
10750 | 10760 |
} |
10751 | 10761 |
/* |
10752 | 10762 |
else if (!superCall) |
... | ... | |
10939 | 10949 |
// name to any errors generated during instantiation (i.e. shared vars error msgs |
10940 | 10950 |
// need to know the procedure name where they are defined, if an error happens) |
10941 | 10951 |
wa.pm.setInstantingExternalProgram(name); |
10942 |
caller = new ExternalProcedureCaller(wa, className, persistent); |
|
10952 |
caller = new ExternalProcedureCaller(wa, name, className, persistent);
|
|
10943 | 10953 |
} |
10944 | 10954 |
catch (Throwable t) |
10945 | 10955 |
{ |
new/src/com/goldencode/p2j/util/InternalEntry.java 2022-12-21 16:40:49 +0000 | ||
---|---|---|
30 | 30 |
** 015 CA 20200605 The Java method instance is resolved on first access of getMethod(). |
31 | 31 |
** 016 ME 20200810 Added qualified name support in signature processing. |
32 | 32 |
** CA 20210218 Fixed signature processing for qualified return type. |
33 |
** CA 20221220 Use lambdas to execute all public methods in an external program. Fixed synchronization |
|
34 |
** in getMethod() and getLambda(), when calling SourceNameMapper.buildLegacyProgram. |
|
33 | 35 |
*/ |
34 | 36 | |
35 | 37 |
/* |
... | ... | |
89 | 91 | |
90 | 92 |
import java.lang.reflect.*; |
91 | 93 |
import java.util.*; |
94 |
import java.util.function.*; |
|
92 | 95 | |
93 | 96 |
import com.goldencode.p2j.util.SourceNameMapper.ExternalProgram; |
94 | 97 | |
... | ... | |
268 | 271 |
* Flag indicating if this is a IN SUPER internal entry (when set to <code>true</code>). |
269 | 272 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
270 | 273 |
*/ |
271 |
private Boolean isSuper = null;
|
|
274 |
private boolean isSuper = false;
|
|
272 | 275 |
|
273 | 276 |
/** |
274 | 277 |
* Flag indicating if this is a IN handle internal entry (when set to <code>true</code>). |
275 | 278 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
276 | 279 |
*/ |
277 |
private Boolean inHandle = null;
|
|
280 |
private boolean inHandle = false;
|
|
278 | 281 |
|
279 | 282 |
/** |
280 | 283 |
* Flag indicating if this is a PRIVATE internal entry (when set to <code>true</code>). |
281 | 284 |
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}. |
282 | 285 |
*/ |
283 |
private Boolean isPrivate = null;
|
|
286 |
private boolean isPrivate = false;
|
|
284 | 287 |
|
285 | 288 |
/** The return type's extent. */ |
286 | 289 |
private int extent = SourceNameMapper.NO_EXTENT; |
... | ... | |
293 | 296 |
*/ |
294 | 297 |
private Method method = null; |
295 | 298 |
|
299 |
/** When not-null, represents a lambda function to execute directly the target. */ |
|
300 |
private BiFunction<Object, Object[], Object> lambda = null; |
|
301 |
|
|
296 | 302 |
/** The OS library name, if this is a native method. */ |
297 | 303 |
private String libname = null; |
298 | 304 |
|
... | ... | |
451 | 457 |
*/ |
452 | 458 |
public Method getMethod() |
453 | 459 |
{ |
454 |
if (method == null && extprog != null && extprog.ooname == null) |
|
460 |
if (method == null && extprog != null && extprog.buildLegacy && extprog.ooname == null)
|
|
455 | 461 |
{ |
456 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
457 |
|
|
458 |
// in case the Java method is not needed for this entry (is a in-handle or in-super), avoid multiple |
|
459 |
// lookups. |
|
460 |
extprog = null; |
|
462 |
synchronized (extprog) |
|
463 |
{ |
|
464 |
if (extprog.buildLegacy) |
|
465 |
{ |
|
466 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
467 |
extprog.buildLegacy = false; |
|
468 |
} |
|
469 |
} |
|
461 | 470 |
} |
462 | 471 |
|
463 | 472 |
return method; |
464 | 473 |
} |
465 | 474 |
|
466 | 475 |
/** |
476 |
* Get the lambda target to be executed, see {@link #lambda}. |
|
477 |
* |
|
478 |
* @return See above. |
|
479 |
*/ |
|
480 |
public BiFunction<Object, Object[], Object> getLambda() |
|
481 |
{ |
|
482 |
if (method == null && extprog != null && extprog.buildLegacy && extprog.ooname == null) |
|
483 |
{ |
|
484 |
synchronized (extprog) |
|
485 |
{ |
|
486 |
if (extprog.buildLegacy) |
|
487 |
{ |
|
488 |
SourceNameMapper.buildLegacyProgram(extprog); |
|
489 |
extprog.buildLegacy = false; |
|
490 |
} |
|
491 |
} |
|
492 |
} |
|
493 |
|
|
494 |
return lambda; |
|
495 |
} |
|
496 |
|
|
497 |
/** |
|
467 | 498 |
* Get the value of the given attribute. If the attribute is null or |
468 | 499 |
* is not set, return <code>null</code>. |
469 | 500 |
* |
... | ... | |
580 | 611 |
} |
581 | 612 |
|
582 | 613 |
/** |
614 |
* Set the target lambda for this internal entry to the specified one. |
|
615 |
* |
|
616 |
* @param lambda |
|
617 |
* The target lambda. |
|
618 |
*/ |
|
619 |
void setLambda(BiFunction<Object, Object[], Object> lambda) |
|
620 |
{ |
|
621 |
this.lambda = lambda; |
|
622 |
} |
|
623 |
|
|
624 |
/** |
|
583 | 625 |
* Set the external program for this entry. |
584 | 626 |
* |
585 | 627 |
* @param extprog |
new/src/com/goldencode/p2j/util/SourceNameMapper.java 2022-12-21 16:39:47 +0000 | ||
---|---|---|
143 | 143 |
** TJD 20220823 Prevent recursive calls to initMappingData() workaround |
144 | 144 |
** TJD 20220824 Prevent recursive calls to initMappingData() workaround in more effective way |
145 | 145 |
** SBI 20221215 Filter the same REST java methods having erased parameters types. |
146 |
** CA 20221220 Use lambdas to execute all public methods in an external program. |
|
146 | 147 |
*/ |
147 | 148 | |
148 | 149 |
/* |
... | ... | |
216 | 217 |
import com.goldencode.p2j.oo.lang.*; |
217 | 218 |
import com.goldencode.p2j.rest.*; |
218 | 219 |
import com.goldencode.p2j.soap.*; |
220 |
import com.goldencode.proxy.*; |
|
219 | 221 |
import com.goldencode.p2j.security.*; |
220 | 222 | |
221 | 223 |
/** |
... | ... | |
2897 | 2899 |
throw new RuntimeException(e); |
2898 | 2900 |
} |
2899 | 2901 | |
2902 |
Map<Method, BiFunction<Object, Object[], Object>> lambdas = LambdaFactory.buildLambdas(cls); |
|
2903 |
|
|
2900 | 2904 |
Method[] methods = cls.getDeclaredMethods(); |
2901 | 2905 |
for (Method mthd : methods) |
2902 | 2906 |
{ |
... | ... | |
2922 | 2926 |
if (ie != null) |
2923 | 2927 |
{ |
2924 | 2928 |
ie.setMethod(mthd); |
2929 |
|
|
2930 |
if (lambdas != null) |
|
2931 |
{ |
|
2932 |
BiFunction<Object, Object[], Object> lambda = lambdas.get(mthd); |
|
2933 |
ie.setLambda(lambda); |
|
2934 |
} |
|
2925 | 2935 |
} |
2926 | 2936 |
} |
2927 | 2937 |
} |
... | ... | |
3334 | 3344 |
InternalEntry main = null; |
3335 | 3345 |
|
3336 | 3346 |
/** |
3347 |
* Flag indicating if {@link SourceNameMapper#buildLegacyClass(Class)} needs to be called when |
|
3348 |
* {@link InternalEntry#getMethod()} or {@link InternalEntry#getLambda()} are accessed. |
|
3349 |
*/ |
|
3350 |
boolean buildLegacy = true; |
|
3351 |
|
|
3352 |
/** |
|
3337 | 3353 |
* Basic c'tor. |
3338 | 3354 |
* |
3339 | 3355 |
* @param pname |
new/src/com/goldencode/p2j/util/Utils.java 2022-12-21 16:37:39 +0000 | ||
---|---|---|
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 | 187 |
** TJD 20220504 Java 11 compatibility minor changes |
188 |
** CA 20221212 Added support for a LambdaInvocationHandler which receives as arguments lambda |
|
189 |
** expressions to perform a direct call of the target, and avoid reflection. |
|
188 | 190 |
*/ |
189 | 191 | |
190 | 192 |
/* |
... | ... | |
1602 | 1604 |
* More, the maps are identity-keyed, for fast access. |
1603 | 1605 |
* |
1604 | 1606 |
* @param method |
1607 |
* The method to execute, only if the function or consumer are both null. |
|
1608 |
* @param f |
|
1609 |
* When not-null, invoke this function and return its value. |
|
1610 |
* @param instance |
|
1611 |
* The instance on which the call will be made; can be <code>null</code> for static methods. |
|
1612 |
* @param args |
|
1613 |
* The method's arguments. |
|
1614 |
* |
|
1615 |
* @return The method's return value. |
|
1616 |
* |
|
1617 |
* @throws IllegalAccessException |
|
1618 |
* @throws IllegalArgumentException |
|
1619 |
* @throws InvocationTargetException |
|
1620 |
*/ |
|
1621 |
public static Object invoke(Method method, |
|
1622 |
BiFunction<Object, Object[], Object> f, |
|
1623 |
Object instance, |
|
1624 |
Object... args) |
|
1625 |
throws IllegalAccessException, |
|
1626 |
IllegalArgumentException, |
|
1627 |
InvocationTargetException |
|
1628 |
{ |
|
1629 |
if (f != null) |
|
1630 |
{ |
|
1631 |
return f.apply(instance, args); |
|
1632 |
} |
|
1633 |
else |
|
1634 |
{ |
|
1635 |
return invoke(method, instance, args); |
|
1636 |
} |
|
1637 |
} |
|
1638 |
|
|
1639 |
/** |
|
1640 |
* Invoke the specified {@link Method} using ReflectASM instead of standard reflection. |
|
1641 |
* <p> |
|
1642 |
* ReflectASM related data is cached in {@link #METHOD_DATA}, thread-local, to avoid the concurrent access. |
|
1643 |
* More, the maps are identity-keyed, for fast access. |
|
1644 |
* |
|
1645 |
* @param method |
|
1605 | 1646 |
* The method to execute. |
1606 | 1647 |
* @param instance |
1607 | 1648 |
* 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:02:28 +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"); |