com.caoccao.javet.interop.proxy.BaseJavetReflectionProxyHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javet-linux-arm64 Show documentation
Show all versions of javet-linux-arm64 Show documentation
Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java.
/*
* Copyright (c) 2021-2024. caoccao.com Sam Cao
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.caoccao.javet.interop.proxy;
import com.caoccao.javet.annotations.*;
import com.caoccao.javet.enums.V8ConversionMode;
import com.caoccao.javet.enums.V8ProxyMode;
import com.caoccao.javet.exceptions.JavetError;
import com.caoccao.javet.exceptions.JavetException;
import com.caoccao.javet.interfaces.IJavetEntityPropertyDescriptor;
import com.caoccao.javet.interop.V8Runtime;
import com.caoccao.javet.interop.binding.ClassDescriptor;
import com.caoccao.javet.interop.binding.IClassProxyPluginFunction;
import com.caoccao.javet.utils.*;
import com.caoccao.javet.values.V8Value;
import com.caoccao.javet.values.primitive.V8ValueString;
import com.caoccao.javet.values.reference.V8ValueObject;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The type Base javet reflection proxy handler.
*
* @param the type parameter
* @param the type parameter
* @since 0.9.6
*/
public abstract class BaseJavetReflectionProxyHandler
extends BaseJavetProxyHandler {
/**
* The constant GETTER_PREFIX_ARRAY.
*
* @since 0.9.6
*/
protected static final String[] GETTER_PREFIX_ARRAY = new String[]{"get", "is"};
/**
* The constant PATTERN_CAPITALIZED_PREFIX.
*
* @since 0.9.7
*/
protected static final Pattern PATTERN_CAPITALIZED_PREFIX = Pattern.compile("^[A-Z]+");
/**
* The constant SETTER_PREFIX_ARRAY.
*
* @since 0.9.6
*/
protected static final String[] SETTER_PREFIX_ARRAY = new String[]{"set", "put"};
/**
* The Class descriptor.
*
* @since 1.1.7
*/
protected ClassDescriptor classDescriptor;
/**
* Instantiates a new Base javet reflection proxy handler.
*
* @param v8Runtime the V8 runtime
* @param targetObject the target object
* @since 0.9.6
*/
public BaseJavetReflectionProxyHandler(V8Runtime v8Runtime, T targetObject) {
super(v8Runtime, targetObject);
classDescriptor = null;
initialize();
}
/**
* Execute.
*
* @param the type parameter
* @param reflectionObjectFactory the reflection object factory
* @param targetObject the target object
* @param thisObject this object
* @param executables the executables
* @param javetVirtualObjects the javet virtual objects
* @return the object
* @throws Throwable the throwable
* @since 0.9.10
*/
protected static Object execute(
IJavetReflectionObjectFactory reflectionObjectFactory,
Object targetObject,
V8ValueObject thisObject,
List executables,
JavetVirtualObject[] javetVirtualObjects) throws Throwable {
List> scoredExecutables = new ArrayList<>();
for (E executable : executables) {
ScoredExecutable scoredExecutable = new ScoredExecutable<>(
reflectionObjectFactory, targetObject, thisObject, executable, javetVirtualObjects);
scoredExecutable.calculateScore();
double score = scoredExecutable.getScore();
if (score > 0) {
scoredExecutables.add(scoredExecutable);
}
}
if (!scoredExecutables.isEmpty()) {
scoredExecutables.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore()));
Throwable lastException = null;
for (ScoredExecutable scoredExecutable : scoredExecutables) {
try {
return scoredExecutable.execute();
} catch (Throwable t) {
lastException = t;
}
}
if (lastException != null) {
throw lastException;
}
}
return null;
}
/**
* Add method.
*
* @param method the method
* @param startIndex the start index
* @param map the map
* @since 0.9.6
*/
protected void addMethod(Method method, int startIndex, Map> map) {
if (method.isAnnotationPresent(V8Function.class)) {
String methodName = method.getName();
String aliasMethodName = method.getAnnotation(V8Function.class).name();
if (!aliasMethodName.isEmpty()) {
methodName = aliasMethodName;
}
List methods = map.computeIfAbsent(methodName, k -> new ArrayList<>());
methods.add(method);
} else {
String methodName = method.getName();
String aliasMethodName = methodName.substring(startIndex);
Matcher matcher = PATTERN_CAPITALIZED_PREFIX.matcher(aliasMethodName);
if (matcher.find()) {
final int capitalizedPrefixLength = matcher.group().length();
if (capitalizedPrefixLength == 1) {
aliasMethodName = methodName.substring(
startIndex, startIndex + capitalizedPrefixLength).toLowerCase(Locale.ROOT)
+ methodName.substring(startIndex + capitalizedPrefixLength);
List methods = map.computeIfAbsent(aliasMethodName, k -> new ArrayList<>());
methods.add(method);
} else {
for (int i = 1; i < capitalizedPrefixLength; ++i) {
aliasMethodName = methodName.substring(startIndex, startIndex + i).toLowerCase(Locale.ROOT)
+ methodName.substring(startIndex + i);
List methods = map.computeIfAbsent(aliasMethodName, k -> new ArrayList<>());
methods.add(method);
}
}
} else {
List methods = map.computeIfAbsent(aliasMethodName, k -> new ArrayList<>());
methods.add(method);
}
}
}
@Override
public V8Value get(V8Value target, V8Value property, V8Value receiver) throws JavetException, E {
V8Value v8Value = internalGet(target, property);
return v8Value == null ? super.get(target, property, receiver) : v8Value;
}
/**
* Gets by field.
*
* @param property the property
* @return the V8 value
* @throws JavetException the javet exception
* @since 1.1.7
*/
protected V8Value getByField(V8Value property) throws JavetException {
if (!classDescriptor.getFieldMap().isEmpty() && property instanceof V8ValueString) {
String propertyName = ((V8ValueString) property).toPrimitive();
Field field = classDescriptor.getFieldMap().get(propertyName);
if (field != null) {
try {
Object callee = Modifier.isStatic(field.getModifiers()) ? null : targetObject;
Object value = field.get(callee);
if (value != null) {
return v8Runtime.toV8Value(value);
}
} catch (JavetException e) {
throw e;
} catch (Throwable t) {
throw new JavetException(JavetError.CallbackUnknownFailure,
SimpleMap.of(JavetError.PARAMETER_MESSAGE, t.getMessage()), t);
}
}
}
return null;
}
/**
* Gets by getter.
*
* @param property the property
* @return the V8 value
* @throws JavetException the javet exception
* @since 1.1.7
*/
protected V8Value getByGetter(V8Value property) throws JavetException {
if (!classDescriptor.getGenericGetters().isEmpty()) {
try {
Object propertyObject = v8Runtime.toObject(property);
if (propertyObject != null && !(propertyObject instanceof V8Value)) {
for (Method method : classDescriptor.getGenericGetters()) {
Class>[] parameterTypes = method.getParameterTypes();
Class> parameterType = parameterTypes[0];
Object key = null;
if (V8Value.class.isAssignableFrom(parameterType)
&& parameterType.isAssignableFrom(property.getClass())) {
key = property;
}
if (key == null && parameterType.isAssignableFrom(propertyObject.getClass())) {
key = propertyObject;
}
if (key == null && parameterType.isPrimitive()) {
key = JavetTypeUtils.toExactPrimitive(parameterType, propertyObject);
}
if (key == null) {
key = JavetTypeUtils.toApproximatePrimitive(parameterType, propertyObject);
}
if (key != null) {
Object callee = Modifier.isStatic(method.getModifiers()) ? null : targetObject;
Object value = method.invoke(callee, key);
if (value != null) {
return v8Runtime.toV8Value(value);
}
}
}
}
} catch (JavetException e) {
throw e;
} catch (Throwable t) {
throw new JavetException(JavetError.CallbackUnknownFailure,
SimpleMap.of(JavetError.PARAMETER_MESSAGE, t.getMessage()), t);
}
}
return null;
}
/**
* Gets by method.
*
* @param target the target
* @param property the property
* @return the V8 value
* @throws JavetException the javet exception
* @throws E the custom exception
* @since 1.1.7
*/
protected V8Value getByMethod(V8Value target, V8Value property) throws JavetException, E {
if (property instanceof V8ValueString) {
String propertyName = ((V8ValueString) property).toPrimitive();
if (!classDescriptor.getClassProxyPlugin().isMethodProxyable(propertyName, classDescriptor.getTargetClass())) {
List methods = classDescriptor.getMethodsMap().get(propertyName);
if (ListUtils.isNotEmpty(methods)) {
JavetReflectionProxyInterceptor reflectionProxyInterceptor = new JavetReflectionProxyInterceptor(
v8Runtime.getConverter().getConfig().getReflectionObjectFactory(),
targetObject,
propertyName,
methods);
return v8Runtime.createV8ValueFunction(reflectionProxyInterceptor.getCallbackContext());
}
methods = classDescriptor.getGettersMap().get(propertyName);
if (ListUtils.isNotEmpty(methods)) {
JavetReflectionProxyInterceptor reflectionProxyInterceptor = new JavetReflectionProxyInterceptor(
v8Runtime.getConverter().getConfig().getReflectionObjectFactory(),
targetObject,
propertyName,
methods);
return reflectionProxyInterceptor.invokeV8Value(target);
}
if (FUNCTION_NAME_TO_V8_VALUE.equals(propertyName)) {
IClassProxyPluginFunction classProxyPluginFunction =
classDescriptor.getClassProxyPlugin().getProxySymbolToPrimitive();
return classProxyPluginFunction.invoke(v8Runtime, targetObject);
}
}
}
return null;
}
/**
* Gets getter prefix length.
*
* @param method the method
* @return the getter prefix length
* @since 0.9.6
*/
protected int getGetterPrefixLength(Method method) {
String methodName = method.getName();
for (String prefix : GETTER_PREFIX_ARRAY) {
if (methodName.startsWith(prefix) && methodName.length() > prefix.length()
&& method.getParameterCount() == 0) {
return prefix.length();
}
}
return 0;
}
@Override
public V8Value getOwnPropertyDescriptor(V8Value target, V8Value property) throws JavetException, E {
V8Value v8Value = null;
try {
if (property instanceof V8ValueString) {
IJavetEntityPropertyDescriptor javetEntityPropertyDescriptor =
classDescriptor.getClassProxyPlugin().getProxyOwnPropertyDescriptor(
targetObject, ((V8ValueString) property).getValue());
if (javetEntityPropertyDescriptor != null) {
v8Value = internalGet(target, property);
if (v8Value != null) {
javetEntityPropertyDescriptor.setValue(v8Value);
return v8Runtime.toV8Value(javetEntityPropertyDescriptor);
}
}
}
} finally {
JavetResourceUtils.safeClose(v8Value);
}
return super.getOwnPropertyDescriptor(target, property);
}
@Override
public V8Value getPrototypeOf(V8Value target) throws JavetException, E {
V8Value v8Value = JavetProxyPrototypeStore.getPrototype(
v8Runtime, getProxyMode(), classDescriptor.getTargetClass());
if (v8Value != null) {
return v8Value;
}
return super.getPrototypeOf(target);
}
/**
* Gets proxy mode.
*
* @return the proxy mode
* @since 3.1.3
*/
public abstract V8ProxyMode getProxyMode();
/**
* Gets setter prefix length.
*
* @param method the method
* @return the setter prefix length
* @since 0.9.6
*/
protected int getSetterPrefixLength(Method method) {
String methodName = method.getName();
for (String prefix : SETTER_PREFIX_ARRAY) {
if (methodName.startsWith(prefix) && methodName.length() > prefix.length()
&& method.getParameterCount() == 1) {
return prefix.length();
}
}
return 0;
}
/**
* Has from generic.
*
* @param property the property
* @return the boolean
* @throws JavetException the javet exception
* @since 1.1.7
*/
protected boolean hasFromGeneric(V8Value property) throws JavetException {
if (!classDescriptor.getGenericGetters().isEmpty()) {
try {
Object propertyObject = v8Runtime.toObject(property);
if (propertyObject != null && !(propertyObject instanceof V8Value)) {
for (Method method : classDescriptor.getGenericGetters()) {
Class>[] parameterTypes = method.getParameterTypes();
Class> parameterType = parameterTypes[0];
Object key = null;
if (V8Value.class.isAssignableFrom(parameterType)
&& parameterType.isAssignableFrom(property.getClass())) {
key = property;
}
if (key == null && parameterType.isAssignableFrom(propertyObject.getClass())) {
key = propertyObject;
}
if (key == null && parameterType.isPrimitive()) {
key = JavetTypeUtils.toExactPrimitive(parameterType, propertyObject);
}
if (key == null) {
key = JavetTypeUtils.toApproximatePrimitive(parameterType, propertyObject);
}
if (key != null) {
Object callee = Modifier.isStatic(method.getModifiers()) ? null : targetObject;
try {
return method.invoke(callee, key) != null;
} catch (Throwable ignored) {
}
}
}
}
} catch (JavetException e) {
throw e;
} catch (Throwable ignored) {
}
}
return false;
}
/**
* Has from regular.
*
* @param property the property
* @return true : has, false: not has
* @since 1.1.7
*/
protected boolean hasFromRegular(V8Value property) {
if (property instanceof V8ValueString) {
String propertyName = ((V8ValueString) property).toPrimitive();
return classDescriptor.getFieldMap().containsKey(propertyName)
|| classDescriptor.getMethodsMap().containsKey(propertyName)
|| classDescriptor.getGettersMap().containsKey(propertyName)
|| classDescriptor.getSettersMap().containsKey(propertyName);
}
return false;
}
/**
* Initialize.
*
* @since 1.1.7
*/
protected abstract void initialize();
/**
* Initialize constructors.
*
* @param currentClass the current class
* @param conversionMode the conversion mode
* @since 1.1.7
*/
protected void initializeConstructors(Class> currentClass, V8ConversionMode conversionMode) {
for (Constructor> constructor : currentClass.getConstructors()) {
if (isAllowed(conversionMode, constructor)) {
classDescriptor.getConstructors().add(constructor);
}
}
}
/**
* Initialize public fields.
*
* @param currentClass the current class
* @param conversionMode the conversion mode
* @param staticMode the static mode
* @since 1.1.7
*/
protected void initializePublicFields(
Class> currentClass, V8ConversionMode conversionMode, boolean staticMode) {
for (Field field : currentClass.getFields()) {
final int fieldModifiers = field.getModifiers();
if (staticMode && !Modifier.isStatic(fieldModifiers)) {
continue;
}
if (!(Modifier.isPublic(fieldModifiers))) {
continue;
}
if (!isAllowed(conversionMode, field)) {
continue;
}
String fieldName = field.getName();
if (field.isAnnotationPresent(V8Property.class)) {
String aliasFieldName = field.getAnnotation(V8Property.class).name();
if (!aliasFieldName.isEmpty()) {
fieldName = aliasFieldName;
}
}
if (classDescriptor.getFieldMap().containsKey(fieldName)) {
continue;
}
JavetReflectionUtils.safeSetAccessible(field);
classDescriptor.getFieldMap().put(fieldName, field);
if (!classDescriptor.getClassProxyPlugin().isUniqueKeySupported(classDescriptor.getTargetClass())) {
classDescriptor.getUniqueKeySet().add(fieldName);
}
}
}
/**
* Initialize public methods.
*
* @param currentClass the current class
* @param conversionMode the conversion mode
* @param staticMode the static mode
* @since 1.1.7
*/
protected void initializePublicMethods(
Class> currentClass, V8ConversionMode conversionMode, boolean staticMode) {
for (Method method : currentClass.getMethods()) {
final int methodModifiers = method.getModifiers();
if (staticMode && !Modifier.isStatic(methodModifiers)) {
continue;
}
if (!(Modifier.isPublic(methodModifiers))) {
continue;
}
if (!isAllowed(conversionMode, method)) {
continue;
}
JavetReflectionUtils.safeSetAccessible(method);
if (isGenericGetter(method)) {
classDescriptor.getGenericGetters().add(method);
} else if (isGenericSetter(method)) {
classDescriptor.getGenericSetters().add(method);
} else if (isApplyFunction(method)) {
classDescriptor.getApplyFunctions().add(method);
} else {
final int getterPrefixLength = getGetterPrefixLength(method);
if (getterPrefixLength > 0) {
addMethod(method, getterPrefixLength, classDescriptor.getGettersMap());
if (!classDescriptor.getClassProxyPlugin().isUniqueKeySupported(classDescriptor.getTargetClass())) {
String aliasMethodName = method.getName().substring(getterPrefixLength);
Matcher matcher = PATTERN_CAPITALIZED_PREFIX.matcher(aliasMethodName);
if (matcher.find()) {
final int capitalizedPrefixLength = matcher.group().length();
if (capitalizedPrefixLength == 1) {
classDescriptor.getUniqueKeySet().add(
aliasMethodName.substring(0, capitalizedPrefixLength).toLowerCase(Locale.ROOT)
+ aliasMethodName.substring(capitalizedPrefixLength));
} else {
classDescriptor.getUniqueKeySet().add(
aliasMethodName.substring(0, capitalizedPrefixLength - 1).toLowerCase(Locale.ROOT)
+ aliasMethodName.substring(capitalizedPrefixLength - 1));
}
}
}
} else {
final int setterPrefixLength = getSetterPrefixLength(method);
if (setterPrefixLength > 0) {
addMethod(method, setterPrefixLength, classDescriptor.getSettersMap());
}
}
}
addMethod(method, 0, classDescriptor.getMethodsMap());
}
}
/**
* Internal get.
*
* @param target the target
* @param property the property
* @return the V8 value
* @throws JavetException the javet exception
* @throws E the custom exception
* @since 3.0.4
*/
protected abstract V8Value internalGet(V8Value target, V8Value property) throws JavetException, E;
/**
* Is allowed.
*
* @param conversionMode the conversion mode
* @param accessibleObject the accessible object
* @return true : allowed, false : disallowed
* @since 1.1.7
*/
protected boolean isAllowed(V8ConversionMode conversionMode, AccessibleObject accessibleObject) {
switch (conversionMode) {
case AllowOnly:
return accessibleObject.isAnnotationPresent(V8Allow.class);
case BlockOnly:
return !accessibleObject.isAnnotationPresent(V8Block.class);
default:
return true;
}
}
/**
* Is apply function.
*
* @param method the method
* @return true : yes, false: no
* @since 1.1.7
*/
protected boolean isApplyFunction(Method method) {
return method.isAnnotationPresent(V8ProxyFunctionApply.class);
}
/**
* Is generic getter.
*
* @param method the method
* @return true : yes, false : no
* @since 0.9.6
*/
protected boolean isGenericGetter(Method method) {
if (method.isAnnotationPresent(V8Getter.class)) {
return true;
}
String methodName = method.getName();
for (String prefix : GETTER_PREFIX_ARRAY) {
if (methodName.equals(prefix) && method.getParameterCount() == 1 && !method.isVarArgs()) {
return true;
}
}
return false;
}
/**
* Is generic setter.
*
* @param method the method
* @return true : yes, false : no
* @since 0.9.6
*/
protected boolean isGenericSetter(Method method) {
if (method.isAnnotationPresent(V8Setter.class)) {
return true;
}
String methodName = method.getName();
for (String prefix : SETTER_PREFIX_ARRAY) {
if (methodName.equals(prefix) && method.getParameterCount() == 2 && !method.isVarArgs()) {
return true;
}
}
return false;
}
/**
* Sets to field.
*
* @param propertyKey the property key
* @param propertyValue the property value
* @return true : set, false: not set
* @throws JavetException the javet exception
* @since 1.1.7
*/
protected boolean setToField(V8Value propertyKey, V8Value propertyValue) throws JavetException {
if (!classDescriptor.getFieldMap().isEmpty() && propertyKey instanceof V8ValueString) {
String propertyName = ((V8ValueString) propertyKey).toPrimitive();
Field field = classDescriptor.getFieldMap().get(propertyName);
if (field != null) {
final int fieldModifiers = field.getModifiers();
if (!Modifier.isFinal(fieldModifiers)) {
try {
Object callee = Modifier.isStatic(fieldModifiers) ? null : targetObject;
field.set(callee, v8Runtime.toObject(propertyValue));
return true;
} catch (JavetException e) {
throw e;
} catch (Throwable t) {
throw new JavetException(JavetError.CallbackUnknownFailure,
SimpleMap.of(JavetError.PARAMETER_MESSAGE, t.getMessage()), t);
}
}
}
}
return false;
}
/**
* Sets to setter.
*
* @param target the target
* @param propertyKey the property key
* @param propertyValue the property value
* @return true : set, false: not set
* @throws JavetException the javet exception
* @since 1.1.7
*/
protected boolean setToSetter(V8Value target, V8Value propertyKey, V8Value propertyValue) throws JavetException {
if (!classDescriptor.getGenericSetters().isEmpty() || !classDescriptor.getSettersMap().isEmpty()) {
Object keyObject, valueObject;
try {
keyObject = v8Runtime.toObject(propertyKey);
valueObject = v8Runtime.toObject(propertyValue);
} catch (JavetException e) {
throw e;
} catch (Throwable t) {
throw new JavetException(JavetError.CallbackUnknownFailure,
SimpleMap.of(JavetError.PARAMETER_MESSAGE, t.getMessage()), t);
}
if (!classDescriptor.getGenericSetters().isEmpty() && keyObject != null && !(keyObject instanceof V8Value)) {
try {
for (Method method : classDescriptor.getGenericSetters()) {
Class>[] parameterTypes = method.getParameterTypes();
Class> keyParameterType = parameterTypes[0];
Class> valueParameterType = parameterTypes[1];
Object key = null;
Object value = null;
if (V8Value.class.isAssignableFrom(keyParameterType)
&& keyParameterType.isAssignableFrom(propertyKey.getClass())) {
key = propertyKey;
}
if (V8Value.class.isAssignableFrom(valueParameterType)
&& valueParameterType.isAssignableFrom(propertyValue.getClass())) {
value = propertyValue;
}
if (key == null && keyParameterType.isAssignableFrom(keyObject.getClass())) {
key = keyObject;
}
if (value == null && valueParameterType.isAssignableFrom(valueObject.getClass())) {
value = valueObject;
}
if (key == null && keyParameterType.isPrimitive()) {
key = JavetTypeUtils.toExactPrimitive(keyParameterType, keyObject);
}
if (value == null && valueParameterType.isPrimitive()) {
value = JavetTypeUtils.toExactPrimitive(valueParameterType, valueObject);
}
if (key == null) {
key = JavetTypeUtils.toApproximatePrimitive(keyParameterType, keyObject);
}
if (value == null) {
value = JavetTypeUtils.toApproximatePrimitive(valueParameterType, valueObject);
}
if (key != null) {
Object callee = Modifier.isStatic(method.getModifiers()) ? null : targetObject;
method.invoke(callee, key, value);
return true;
}
}
} catch (Throwable t) {
throw new JavetException(JavetError.CallbackUnknownFailure,
SimpleMap.of(JavetError.PARAMETER_MESSAGE, t.getMessage()), t);
}
}
if (keyObject instanceof String) {
String propertyName = (String) keyObject;
List methods = classDescriptor.getSettersMap().get(propertyName);
if (methods != null) {
JavetReflectionProxyInterceptor reflectionProxyInterceptor = new JavetReflectionProxyInterceptor(
v8Runtime.getConverter().getConfig().getReflectionObjectFactory(),
targetObject,
propertyName,
methods);
reflectionProxyInterceptor.invokeObject((V8ValueObject) target, propertyValue);
return true;
}
}
}
return false;
}
}