From 3b90e544a78b1546c0808b5c26a43d01d30b9bca Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 4 Jul 2016 22:01:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annimon/ownlang/lib/FunctionValue.java | 2 +- src/com/annimon/ownlang/lib/modules/java.java | 459 ++++++++++++++++++ test/interop/Data.java | 39 ++ test/resources/modules/java/classes.own | 49 ++ 4 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 src/com/annimon/ownlang/lib/modules/java.java create mode 100644 test/interop/Data.java create mode 100644 test/resources/modules/java/classes.own diff --git a/src/com/annimon/ownlang/lib/FunctionValue.java b/src/com/annimon/ownlang/lib/FunctionValue.java index 97ad62a..16108c6 100644 --- a/src/com/annimon/ownlang/lib/FunctionValue.java +++ b/src/com/annimon/ownlang/lib/FunctionValue.java @@ -7,7 +7,7 @@ import java.util.Objects; * * @author aNNiMON */ -public final class FunctionValue implements Value { +public class FunctionValue implements Value { public static final FunctionValue EMPTY = new FunctionValue(args -> NumberValue.ZERO); diff --git a/src/com/annimon/ownlang/lib/modules/java.java b/src/com/annimon/ownlang/lib/modules/java.java new file mode 100644 index 0000000..feb4147 --- /dev/null +++ b/src/com/annimon/ownlang/lib/modules/java.java @@ -0,0 +1,459 @@ +package com.annimon.ownlang.lib.modules; + +import com.annimon.ownlang.lib.*; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * Java interoperability module. + * + * @author aNNiMON + */ +public final class java implements Module { + + private static final Value NULL = new NullValue(); + + @Override + public void init() { + Variables.define("null", NULL); + Variables.define("boolean.class", new ClassValue(boolean.class)); + Variables.define("boolean[].class", new ClassValue(boolean[].class)); + Variables.define("boolean[][].class", new ClassValue(boolean[][].class)); + Variables.define("byte.class", new ClassValue(byte.class)); + Variables.define("byte[].class", new ClassValue(byte[].class)); + Variables.define("byte[][].class", new ClassValue(byte[][].class)); + Variables.define("short.class", new ClassValue(short.class)); + Variables.define("short[].class", new ClassValue(short[].class)); + Variables.define("short[][].class", new ClassValue(short[][].class)); + Variables.define("char.class", new ClassValue(char.class)); + Variables.define("char[].class", new ClassValue(char[].class)); + Variables.define("char[][].class", new ClassValue(char[][].class)); + Variables.define("int.class", new ClassValue(int.class)); + Variables.define("int[].class", new ClassValue(int[].class)); + Variables.define("int[][].class", new ClassValue(int[][].class)); + Variables.define("long.class", new ClassValue(long.class)); + Variables.define("long[].class", new ClassValue(long[].class)); + Variables.define("long[][].class", new ClassValue(long[][].class)); + Variables.define("float.class", new ClassValue(float.class)); + Variables.define("float[].class", new ClassValue(float[].class)); + Variables.define("float[][].class", new ClassValue(float[][].class)); + Variables.define("double.class", new ClassValue(double.class)); + Variables.define("double[].class", new ClassValue(double[].class)); + Variables.define("double[][].class", new ClassValue(double[][].class)); + Variables.define("String.class", new ClassValue(String.class)); + Variables.define("String[].class", new ClassValue(String[].class)); + Variables.define("String[][].class", new ClassValue(String[][].class)); + Variables.define("Object.class", new ClassValue(Object.class)); + Variables.define("Object[].class", new ClassValue(Object[].class)); + Variables.define("Object[][].class", new ClassValue(Object[][].class)); + + Functions.set("isNull", this::isNull); + Functions.set("newClass", this::newClass); + Functions.set("toObject", this::toObject); + Functions.set("toValue", this::toValue); + } + + // + private static class NullValue implements Value { + + @Override + public Object raw() { + return null; + } + + @Override + public int asInt() { + return 0; + } + + @Override + public double asNumber() { + return 0; + } + + @Override + public String asString() { + return "null"; + } + + @Override + public int type() { + return 482862660; + } + + @Override + public int compareTo(Value o) { + if (o.raw() == null) return 0; + return -1; + } + + @Override + public String toString() { + return asString(); + } + } + + private static class ClassValue extends MapValue { + + public static Value classOrNull(Class clazz) { + if (clazz == null) return NULL; + return new ClassValue(clazz); + } + + private final Class clazz; + + public ClassValue(Class clazz) { + super(25); + this.clazz = clazz; + init(clazz); + } + + private void init(Class clazz) { + set(new StringValue("isAnnotation"), NumberValue.fromBoolean(clazz.isAnnotation())); + set(new StringValue("isAnonymousClass"), NumberValue.fromBoolean(clazz.isAnonymousClass())); + set(new StringValue("isArray"), NumberValue.fromBoolean(clazz.isArray())); + set(new StringValue("isEnum"), NumberValue.fromBoolean(clazz.isEnum())); + set(new StringValue("isInterface"), NumberValue.fromBoolean(clazz.isInterface())); + set(new StringValue("isLocalClass"), NumberValue.fromBoolean(clazz.isLocalClass())); + set(new StringValue("isMemberClass"), NumberValue.fromBoolean(clazz.isMemberClass())); + set(new StringValue("isPrimitive"), NumberValue.fromBoolean(clazz.isPrimitive())); + set(new StringValue("isSynthetic"), NumberValue.fromBoolean(clazz.isSynthetic())); + + set(new StringValue("modifiers"), NumberValue.of(clazz.getModifiers())); + + set(new StringValue("canonicalName"), new StringValue(clazz.getCanonicalName())); + set(new StringValue("name"), new StringValue(clazz.getName())); + set(new StringValue("simpleName"), new StringValue(clazz.getSimpleName())); + set(new StringValue("typeName"), new StringValue(clazz.getTypeName())); + set(new StringValue("genericString"), new StringValue(clazz.toGenericString())); + + set(new StringValue("getComponentType"), new FunctionValue(v -> classOrNull(clazz.getComponentType()) )); + set(new StringValue("getDeclaringClass"), new FunctionValue(v -> classOrNull(clazz.getDeclaringClass()) )); + set(new StringValue("getEnclosingClass"), new FunctionValue(v -> classOrNull(clazz.getEnclosingClass()) )); + set(new StringValue("getSuperclass"), new FunctionValue(v -> new ClassValue(clazz.getSuperclass()) )); + + set(new StringValue("getClasses"), new FunctionValue(v -> array(clazz.getClasses()) )); + set(new StringValue("getDeclaredClasses"), new FunctionValue(v -> array(clazz.getDeclaredClasses()) )); + set(new StringValue("getInterfaces"), new FunctionValue(v -> array(clazz.getInterfaces()) )); + + set(new StringValue("asSubclass"), new FunctionValue(this::asSubclass)); + set(new StringValue("isAssignableFrom"), new FunctionValue(this::isAssignableFrom)); + set(new StringValue("new"), new FunctionValue(this::newInstance)); + set(new StringValue("cast"), new FunctionValue(this::cast)); + } + + private Value asSubclass(Value... args) { + Arguments.check(1, args.length); + return new ClassValue(clazz.asSubclass( ((ClassValue)args[0]).clazz )); + } + + private Value isAssignableFrom(Value... args) { + Arguments.check(1, args.length); + return NumberValue.fromBoolean(clazz.isAssignableFrom( ((ClassValue)args[0]).clazz )); + } + + private Value newInstance(Value... args) { + try { + return new ObjectValue(clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException ex) { + return NULL; + } + } + + private Value cast(Value... args) { + Arguments.check(1, args.length); + return objectToValue(clazz, clazz.cast(((ObjectValue)args[0]).object)); + } + + @Override + public String toString() { + return "ClassValue " + clazz.toString(); + } + } + + private static class ObjectValue extends MapValue { + + public static Value objectOrNull(Object object) { + if (object == null) return NULL; + return new ObjectValue(object); + } + + private final Object object; + + public ObjectValue(Object object) { + super(2); + this.object = object; + } + + @Override + public boolean containsKey(Value key) { + return getValue(key.asString()) != null; + } + + @Override + public Value get(Value key) { + return getValue(key.asString()); + } + + private Value getValue(String key) { + // Trying to get field + try { + final Field field = object.getClass().getField(key); + return objectToValue(field.getType(), field.get(object)); + } catch (NoSuchFieldException | SecurityException | + IllegalArgumentException | IllegalAccessException ex) { + // ignore and go to the next step + } + + // Trying to invoke method + try { + final Method[] allMethods = object.getClass().getMethods(); + final List methods = new ArrayList<>(); + for (Method method : allMethods) { + if (method.getName().equals(key)) { + methods.add(method); + } + } + if (methods.size() == 0) { + return FunctionValue.EMPTY; + } + return new FunctionValue(methodsToFunction(methods)); + } catch (SecurityException ex) { + // ignore and go to the next step + } + + return NULL; + + } + + private Function methodsToFunction(List methods) { + return (args) -> { + for (Method method : methods) { + if (method.getParameterCount() != args.length) continue; + if (!isMatch(args, method.getParameterTypes())) continue; + try { + final Object result = method.invoke(object, valuesToObjects(args)); + if (method.getReturnType() != void.class) { + return objectToValue(result); + } + return NumberValue.ONE; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + // skip + } + } + return null; + }; + } + + private boolean isMatch(Value[] args, Class[] types) { + for (int i = 0; i < args.length; i++) { + final Value arg = args[i]; + final Class clazz = types[i]; + + if (arg == NULL) continue; + if (unboxed(clazz).isAssignableFrom(unboxed(valueToObject(arg).getClass()))) { + continue; + } + return false; + } + return true; + } + + @Override + public String asString() { + return object.toString(); + } + + @Override + public String toString() { + return "ObjectValue " + asString(); + } + + private Class unboxed(Class clazz) { + if (clazz == null) return null; + if (clazz.isPrimitive()) { + if (int.class == clazz) return Integer.class; + if (boolean.class == clazz) return Boolean.class; + if (double.class == clazz) return Double.class; + if (float.class == clazz) return Float.class; + if (long.class == clazz) return Long.class; + if (byte.class == clazz) return Byte.class; + if (char.class == clazz) return Character.class; + if (short.class == clazz) return Short.class; + if (void.class == clazz) return Void.class; + } + return clazz; + } + } +// + + private Value isNull(Value... args) { + Arguments.checkAtLeast(1, args.length); + for (Value arg : args) { + if (arg.raw() == null) return NumberValue.ONE; + } + return NumberValue.ZERO; + } + + private Value newClass(Value... args) { + Arguments.check(1, args.length); + + final String className = args[0].asString(); + try { + return new ClassValue(Class.forName(className)); + } catch (ClassNotFoundException ce) { + return NULL; + } + } + + private Value toObject(Value... args) { + Arguments.check(1, args.length); + if (args[0] == NULL) return NULL; + return new ObjectValue(valueToObject(args[0])); + } + + private Value toValue(Value... args) { + Arguments.check(1, args.length); + if (args[0] instanceof ObjectValue) { + return objectToValue( ((ObjectValue) args[0]).object ); + } + return NULL; + } + + + // + private static ArrayValue array(Class[] classes) { + final ArrayValue result = new ArrayValue(classes.length); + for (int i = 0; i < classes.length; i++) { + result.set(i, ClassValue.classOrNull(classes[i])); + } + return result; + } + + private static Value objectToValue(Object o) { + if (o == null) return NULL; + return objectToValue(o.getClass(), o); + } + + private static Value objectToValue(Class clazz, Object o) { + if (o == null | o == NULL) return NULL; + if (clazz.isPrimitive()) { + if (int.class.isAssignableFrom(clazz)) + return NumberValue.of((int) o); + if (boolean.class.isAssignableFrom(clazz)) + return NumberValue.fromBoolean((boolean) o); + if (double.class.isAssignableFrom(clazz)) + return NumberValue.of((double) o); + if (float.class.isAssignableFrom(clazz)) + return NumberValue.of((float) o); + if (long.class.isAssignableFrom(clazz)) + return NumberValue.of((long) o); + if (byte.class.isAssignableFrom(clazz)) + return NumberValue.of((byte) o); + if (char.class.isAssignableFrom(clazz)) + return NumberValue.of((char) o); + if (short.class.isAssignableFrom(clazz)) + return NumberValue.of((short) o); + } + if (Number.class.isAssignableFrom(clazz)) { + return NumberValue.of((Number) o); + } + if (String.class.isAssignableFrom(clazz)) { + return new StringValue((String) o); + } + if (CharSequence.class.isAssignableFrom(clazz)) { + return new StringValue( ((CharSequence) o).toString() ); + } + if (o instanceof Value) { + return (Value) o; + } + if (clazz.isArray()) { + final int length = Array.getLength(o); + final ArrayValue result = new ArrayValue(length); + final Class componentType = clazz.getComponentType(); + int i = 0; + if (boolean.class.isAssignableFrom(componentType)) { + for (boolean element : (boolean[]) o) { + result.set(i++, NumberValue.fromBoolean(element)); + } + } else if (byte.class.isAssignableFrom(componentType)) { + for (byte element : (byte[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (char.class.isAssignableFrom(componentType)) { + for (char element : (char[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (double.class.isAssignableFrom(componentType)) { + for (double element : (double[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (float.class.isAssignableFrom(componentType)) { + for (float element : (float[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (int.class.isAssignableFrom(componentType)) { + for (int element : (int[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (long.class.isAssignableFrom(componentType)) { + for (long element : (long[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else if (short.class.isAssignableFrom(componentType)) { + for (short element : (short[]) o) { + result.set(i++, NumberValue.of(element)); + } + } else { + for (Object element : (Object[]) o) { + result.set(i++, objectToValue(element)); + } + } + return result; + } + final Class componentType = clazz.getComponentType(); + if (componentType != null) { + return objectToValue(componentType, o); + } + return new ObjectValue(o); + } + + private static Object[] valuesToObjects(Value[] args) { + Object[] result = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + result[i] = valueToObject(args[i]); + } + return result; + } + + private static Object valueToObject(Value value) { + if (value == NULL) return null; + switch (value.type()) { + case Types.NUMBER: + return value.raw(); + case Types.STRING: + return value.asString(); + case Types.ARRAY: { + final ArrayValue array = (ArrayValue) value; + final int size = array.size(); + final Object[] result = new Object[size]; + for (int i = 0; i < size; i++) { + result[i] = valueToObject(array.get(i)); + } + return result; + } + } + if (value instanceof ObjectValue) { + return ((ObjectValue) value).object; + } + if (value instanceof ClassValue) { + return ((ClassValue) value).clazz; + } + return value.raw(); + } +// +} diff --git a/test/interop/Data.java b/test/interop/Data.java new file mode 100644 index 0000000..693cfb8 --- /dev/null +++ b/test/interop/Data.java @@ -0,0 +1,39 @@ +package interop; + +public final class Data { + public final int intValue = 3228; + public final int[] intArrayValue = {1, 2, 3}; + public final Object nullObject = null; + public final String stringValue = "str"; + public final String[] stringArrayValue = {"abc", "test"}; + public final Object[] objectArray = new Object[] {intValue, intArrayValue, nullObject, stringValue, stringArrayValue}; + public final Object compoundObject = objectArray; + + public void method() { + System.out.println("method"); + } + + public String methodWithResult() { + return "result"; + } + + + private int value; + private String text; + + public void set(int value) { + this.value = value; + } + + public void set(String text) { + this.text = text; + } + + public int getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/test/resources/modules/java/classes.own b/test/resources/modules/java/classes.own new file mode 100644 index 0000000..04a8dd2 --- /dev/null +++ b/test/resources/modules/java/classes.own @@ -0,0 +1,49 @@ +use "std" +use "java" + +def testCheckNull() { + assertTrue(isNull(null)) + assertFalse(isNull(`byte[].class`)) + assertTrue(null == null) +} + +def testFieldAccess() { + data = createObject() + assertEquals(3228, data.intValue) + assertEquals([1, 2, 3], data.intArrayValue) + assertTrue(isNull(data.nullObject)) + assertEquals("str", data.stringValue) + assertEquals(["abc", "test"], data.stringArrayValue) + assertObjectArray(data.objectArray) +} + +def testCast() { + data = createObject() + assertObjectArray( `Object[].class`.cast(data.compoundObject) ) +} + +def testInvokeMethod() { + data = createObject() + data.method() + assertEquals("result", data.methodWithResult()) +} + +def testInvokeMethodSameName() { + data = createObject() + data.set(1) + data.set("text") + + assertEquals(1, data.getValue()) + assertEquals("text", data.getText()) +} + + +def createObject() { + dataClass = newClass("interop.Data") + return dataClass.new() +} + +def assertObjectArray(obj) { + assertEquals(5, length(obj)) + assertEquals([3228, [1, 2, 3], null, "str", ["abc", "test"]], obj) +}