
com.koushikdutta.quack.JavaObject Maven / Gradle / Ivy
package com.koushikdutta.quack;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.koushikdutta.quack.QuackContext.isEmpty;
@SuppressWarnings({"unchecked", "rawtypes"})
public final class JavaObject implements QuackObject, QuackJavaObject {
private final Object target;
private final QuackContext quackContext;
public JavaObject(QuackContext quackContext, Object target) {
this.quackContext = quackContext;
this.target = target;
}
@Override
public Object getObject() {
return target;
}
public static Method getGetterMethod(String key, Method[] methods) {
return QuackContext.javaObjectGetter.memoize(() -> {
for (Method method : methods) {
// name match, no args, and a return type
if (method.getParameterTypes().length != 0)
continue;
if (method.getReturnType() == void.class || method.getReturnType() == Void.class)
continue;
QuackProperty property = method.getAnnotation(QuackProperty.class);
if (property == null)
continue;
String propName = property.name();
if (isEmpty(propName))
propName = method.getName();
if (propName.equals(key))
return method;
}
return null;
}, key, methods);
}
public static Method getSetterMethod(String key, Method[] methods) {
return QuackContext.javaObjectSetter.memoize(() -> {
for (Method method : methods) {
// name match, no args, and a return type
if (method.getParameterTypes().length != 1)
continue;
if (method.getReturnType() != void.class && method.getReturnType() != Void.class)
continue;
QuackProperty property = method.getAnnotation(QuackProperty.class);
if (property == null)
continue;
String propName = property.name();
if (isEmpty(propName))
propName = method.getName();
if (propName.equals(key))
return method;
}
return null;
}, key, methods);
}
private static boolean hasMethod(Class clazz, String key, boolean requiresStatic) {
// try to get methods
for (Method method : clazz.getMethods()) {
if (requiresStatic && !Modifier.isStatic(method.getModifiers()))
continue;
if (method.getName().equals(key))
return true;
QuackMethodName annotation = method.getAnnotation(QuackMethodName.class);
if (annotation != null && annotation.name().equals(key))
return true;
}
return false;
}
private Field findField(String key, Class clazz) {
return QuackContext.javaObjectFields.memoize(() -> {
// try to get fields
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().equals(key) && ((field.getModifiers() & Modifier.STATIC) == 0) && ((field.getModifiers() & Modifier.PUBLIC) != 0))
return field;
}
if (target instanceof Class) {
for (Field field : ((Class)target).getDeclaredFields()) {
if (field.getName().equals(key) && ((field.getModifiers() & Modifier.STATIC) != 0) && ((field.getModifiers() & Modifier.PUBLIC) != 0))
return field;
}
}
return null;
}, key, clazz.getDeclaredFields());
}
public Object get(String key) {
Object ret = getMap(key);
if (ret != null)
return ret;
Class clazz = target.getClass();
if (!Proxy.isProxyClass(clazz)) {
// length is not a field of the array class. it's a language property.
// Nor can arrays be cast to Array.
if (clazz.isArray() && "length".equals(key))
return Array.getLength(target);
Field f = findField(key, clazz);
if (f != null) {
try {
return quackContext.coerceJavaToJavaScript(f.get(target));
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
}
}
Method g = getGetterMethod(key, clazz.getMethods());
if (g != null) {
try {
return quackContext.coerceJavaToJavaScript(g.invoke(target));
}
catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
Boolean m = QuackContext.javaObjectMethods.memoize(() -> {
if (hasMethod(clazz, key, false))
return true;
if (target instanceof Class)
return hasMethod((Class)target, key, true);
return false;
}, key, clazz.getMethods());
if (m)
return new JavaMethodObject(quackContext, target, key);
return null;
}
public Object get(int index) {
if (target.getClass().isArray())
return Array.get(target, index);
if (target instanceof List)
return ((List)target).get(index);
return null;
}
private Object getMap(Object key) {
if (target instanceof Map)
return quackContext.coerceJavaToJavaScript(((Map)target).get(key));
return null;
}
@Override
public Object get(Object key) {
if (key instanceof String)
return get((String)key);
if (key instanceof Number) {
Number number = (Number)key;
if (number.doubleValue() == number.intValue())
return get(number.intValue());
}
return getMap(key);
}
private void noSet() {
throw new UnsupportedOperationException("can not set value on this JavaObject");
}
public boolean set(int index, Object value) {
if (target instanceof Array) {
Array.set(target, index, value);
return true;
}
if (target instanceof List) {
((List)target).set(index, value);
return true;
}
noSet();
return false;
}
private boolean putMap(Object key, Object value) {
if (target instanceof Map) {
((Map)target).put(key, value);
return true;
}
noSet();
return false;
}
public boolean set(String key, Object value) {
Class clazz = target.getClass();
Field f = findField(key, clazz);
if (f != null) {
try {
f.set(target, quackContext.coerceJavaScriptToJava(f.getType(), value));
return true;
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
}
Method s = getSetterMethod(key, clazz.getMethods());
if (s != null) {
try {
quackContext.coerceJavaToJavaScript(s.invoke(target, quackContext.coerceJavaScriptToJava(s.getParameterTypes()[0], value)));
}
catch (Exception e) {
throw new IllegalArgumentException(e);
}
return true;
}
return putMap(key, value);
}
@Override
public boolean set(Object key, Object value) {
if (key instanceof Number) {
Number number = (Number)key;
if (number.doubleValue() == number.intValue()) {
return set(number.intValue(), value);
}
}
if (key instanceof String) {
return set((String)key, value);
}
return putMap(key, value);
}
@Override
public Object callMethod(Object thiz, Object... args) {
throw new UnsupportedOperationException("can not call " + target);
}
public Object callProperty(Object property, Object... args) {
if (property == null)
throw new NullPointerException();
property = get(property);
if (property instanceof QuackObject)
return ((QuackObject)property).callMethod(this, args);
throw new UnsupportedOperationException("can not call " + target);
}
@Override
public Object construct(Object... args) {
if (!(target instanceof Class))
return QuackObject.super.construct(args);
Class clazz = (Class)target;
Constructor[] constructors = clazz.getConstructors();
if (constructors.length == 0) {
try {
return clazz.newInstance();
}
catch (Exception e) {
return new IllegalArgumentException(e);
}
}
ArrayList argTypes = new ArrayList<>();
for (Object arg: args) {
if (arg == null)
argTypes.add(null);
else
argTypes.add(arg.getClass());
}
Constructor best = QuackContext.javaObjectConstructorCandidates.memoize(() -> {
Constructor ret = null;
int bestScore = Integer.MAX_VALUE;
for (Constructor constructor: constructors) {
// parameter count is most important
int score = Math.abs(argTypes.size() - constructor.getParameterTypes().length) * 1000;
// tiebreak by checking parameter types
for (int i = 0; i < Math.min(constructor.getParameterTypes().length, argTypes.size()); i++) {
// check if the class is assignable or both parameters are numbers
Class> argType = argTypes.get(i);
Class> paramType = constructor.getParameterTypes()[i];
if (paramType == argType) {
score -= 4;
}
if (QuackContext.isNumberClass(paramType) && QuackContext.isNumberClass(argType)) {
score -= 3;
}
else if ((paramType == Long.class || paramType == long.class) && argType == String.class) {
score -= 2;
}
else if (argType == null || paramType.isAssignableFrom(argType)) {
score -= 1;
}
}
if (score < bestScore) {
bestScore = score;
ret = constructor;
}
}
return ret;
}, target, constructors, argTypes.toArray());
try {
int numParameters = best.getParameterTypes().length;
if (best.isVarArgs())
numParameters--;
ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy