From 483c7c1242fc8f53f073239fac575c0c1ff1cb4b Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 3 Oct 2019 20:36:38 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=9F=D0=BE=D0=B8=D1=81=D0=BA=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D1=8F=D1=89=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B2=20java::new?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annimon/ownlang/modules/java/java.java | 28 +++++++++++++------ src/test/resources/modules/java/classes.own | 6 ++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/annimon/ownlang/modules/java/java.java b/src/main/java/com/annimon/ownlang/modules/java/java.java index 953ae2c..c8f7822 100644 --- a/src/main/java/com/annimon/ownlang/modules/java/java.java +++ b/src/main/java/com/annimon/ownlang/modules/java/java.java @@ -3,6 +3,7 @@ package com.annimon.ownlang.modules.java; import com.annimon.ownlang.lib.*; import com.annimon.ownlang.modules.Module; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -156,17 +157,13 @@ public final class java implements Module { return new ClassValue(clazz.asSubclass( ((ClassValue)args[0]).clazz )); } - private Value isAssignableFrom(Value... args) { + 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 newInstance(Value[] args) { + return findConstructorAndInstantiate(args, clazz.getConstructors()); } private Value cast(Value... args) { @@ -293,6 +290,21 @@ public final class java implements Module { return NULL; } + + private static Value findConstructorAndInstantiate(Value[] args, Constructor[] ctors) { + for (Constructor ctor : ctors) { + if (ctor.getParameterCount() != args.length) continue; + if (!isMatch(args, ctor.getParameterTypes())) continue; + try { + final Object result = ctor.newInstance(valuesToObjects(args)); + return new ObjectValue(result); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + // skip + } + } + return null; + } private static Function methodsToFunction(Object object, List methods) { return (args) -> { @@ -312,7 +324,7 @@ public final class java implements Module { return null; }; } - + private static boolean isMatch(Value[] args, Class[] types) { for (int i = 0; i < args.length; i++) { final Value arg = args[i]; diff --git a/src/test/resources/modules/java/classes.own b/src/test/resources/modules/java/classes.own index 14ddbc6..bfd12ce 100644 --- a/src/test/resources/modules/java/classes.own +++ b/src/test/resources/modules/java/classes.own @@ -36,6 +36,12 @@ def testInvokeMethodSameName() { assertEquals("text", data.getText()) } +def testNonDefaultConstructor() { + StringBuilder = newClass("java.lang.StringBuilder") + sb = StringBuilder.`new`("text") + assertEquals("text", sb.toString()) +} + def createObject() { dataClass = newClass("interop.Data") From 826891ac52bc254ba91b132ba321eed971a44caa Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 3 Oct 2019 21:04:17 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B8=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=86=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20java-=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B0=20=D1=81=20=D0=BF=D0=BE=D0=BC=D0=BE?= =?UTF-8?q?=D1=89=D1=8C=D1=8E=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=20new?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ownlang/lib/ClassDeclarations.java | 10 ++-------- .../com/annimon/ownlang/lib/Instantiable.java | 9 +++++++++ .../annimon/ownlang/modules/java/java.java | 5 +++-- .../parser/ast/ObjectCreationExpression.java | 20 ++++++++++++++++--- 4 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/annimon/ownlang/lib/Instantiable.java diff --git a/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java b/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java index 1335b1b..b079ef3 100644 --- a/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java +++ b/src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java @@ -1,15 +1,14 @@ package com.annimon.ownlang.lib; -import com.annimon.ownlang.exceptions.UnknownFunctionException; import com.annimon.ownlang.parser.ast.ClassDeclarationStatement; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public final class ClassDeclarations { private static final Map declarations; static { - declarations = new HashMap<>(); + declarations = new ConcurrentHashMap<>(); } private ClassDeclarations() { } @@ -22,12 +21,7 @@ public final class ClassDeclarations { return declarations; } - public static boolean isExists(String key) { - return declarations.containsKey(key); - } - public static ClassDeclarationStatement get(String key) { - if (!isExists(key)) throw new UnknownFunctionException(key); return declarations.get(key); } diff --git a/src/main/java/com/annimon/ownlang/lib/Instantiable.java b/src/main/java/com/annimon/ownlang/lib/Instantiable.java new file mode 100644 index 0000000..709b4c7 --- /dev/null +++ b/src/main/java/com/annimon/ownlang/lib/Instantiable.java @@ -0,0 +1,9 @@ +package com.annimon.ownlang.lib; + +/** + * Interface for values that supports creating instances with `new` keyword. + */ +public interface Instantiable { + + Value newInstance(Value[] args); +} diff --git a/src/main/java/com/annimon/ownlang/modules/java/java.java b/src/main/java/com/annimon/ownlang/modules/java/java.java index c8f7822..58ef5fc 100644 --- a/src/main/java/com/annimon/ownlang/modules/java/java.java +++ b/src/main/java/com/annimon/ownlang/modules/java/java.java @@ -103,7 +103,7 @@ public final class java implements Module { } } - private static class ClassValue extends MapValue { + private static class ClassValue extends MapValue implements Instantiable { public static Value classOrNull(Class clazz) { if (clazz == null) return NULL; @@ -162,7 +162,8 @@ public final class java implements Module { return NumberValue.fromBoolean(clazz.isAssignableFrom( ((ClassValue)args[0]).clazz )); } - private Value newInstance(Value[] args) { + @Override + public Value newInstance(Value[] args) { return findConstructorAndInstantiate(args, clazz.getConstructors()); } diff --git a/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java b/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java index e5edc27..6e15ad0 100644 --- a/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java +++ b/src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java @@ -1,5 +1,6 @@ package com.annimon.ownlang.parser.ast; +import com.annimon.ownlang.exceptions.UnknownClassException; import com.annimon.ownlang.lib.*; import java.util.Iterator; import java.util.List; @@ -17,6 +18,16 @@ public final class ObjectCreationExpression implements Expression { @Override public Value eval() { final ClassDeclarationStatement cd = ClassDeclarations.get(className); + if (cd == null) { + // Is Instantiable? + if (Variables.isExists(className)) { + final Value variable = Variables.get(className); + if (variable instanceof Instantiable) { + return ((Instantiable) variable).newInstance(ctorArgs()); + } + } + throw new UnknownClassException(className); + } // Create an instance and put evaluated fields with method declarations final ClassInstanceValue instance = new ClassInstanceValue(className); @@ -30,14 +41,17 @@ public final class ObjectCreationExpression implements Expression { } // Call a constructor + instance.callConstructor(ctorArgs()); + return instance; + } + + private Value[] ctorArgs() { final int argsSize = constructorArguments.size(); final Value[] ctorArgs = new Value[argsSize]; for (int i = 0; i < argsSize; i++) { ctorArgs[i] = constructorArguments.get(i).eval(); } - instance.callConstructor(ctorArgs); - - return instance; + return ctorArgs; } @Override From f03cbb6fc741aa92fbec8d877d152270a4ba32c5 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 4 Oct 2019 00:21:28 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D1=82=D0=B8=D0=BF=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BC=D0=B0=D1=81=D1=81=D0=B8=D0=B2=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B2=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=20java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annimon/ownlang/modules/java/java.java | 84 +++++++++++++++++-- src/test/resources/modules/java/classes.own | 2 +- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/annimon/ownlang/modules/java/java.java b/src/main/java/com/annimon/ownlang/modules/java/java.java index 58ef5fc..890f89b 100644 --- a/src/main/java/com/annimon/ownlang/modules/java/java.java +++ b/src/main/java/com/annimon/ownlang/modules/java/java.java @@ -8,6 +8,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -167,7 +168,7 @@ public final class java implements Module { return findConstructorAndInstantiate(args, clazz.getConstructors()); } - private Value cast(Value... args) { + private Value cast(Value[] args) { Arguments.check(1, args.length); return objectToValue(clazz, clazz.cast(((ObjectValue)args[0]).object)); } @@ -337,7 +338,15 @@ public final class java implements Module { boolean assignable = unboxed != null; final Object object = valueToObject(arg); assignable = assignable && (object != null); - assignable = assignable && (unboxed.isAssignableFrom(object.getClass())); + if (assignable && unboxed.isArray() && object.getClass().isArray()) { + final Class uComponentType = unboxed.getComponentType(); + final Class oComponentType = object.getClass().getComponentType(); + assignable = assignable && (uComponentType != null); + assignable = assignable && (oComponentType != null); + assignable = assignable && (uComponentType.isAssignableFrom(oComponentType)); + } else { + assignable = assignable && (unboxed.isAssignableFrom(object.getClass())); + } if (assignable) continue; return false; @@ -460,7 +469,7 @@ public final class java implements Module { } return result; } - + private static Object[] valuesToObjects(Value[] args) { Object[] result = new Object[args.length]; for (int i = 0; i < args.length; i++) { @@ -490,11 +499,72 @@ public final class java implements Module { private static Object arrayToObject(ArrayValue value) { final int size = value.size(); - final Object[] result = new Object[size]; - for (int i = 0; i < size; i++) { - result[i] = valueToObject(value.get(i)); + final Object[] array = new Object[size]; + if (size == 0) { + return array; + } + + Class elementsType = null; + for (int i = 0; i < size; i++) { + array[i] = valueToObject(value.get(i)); + if (i == 0) { + elementsType = array[0].getClass(); + } else { + elementsType = mostCommonType(elementsType, array[i].getClass()); + } + } + + if (elementsType.equals(Object[].class)) { + return array; + } + return typedArray(array, size, elementsType); + } + + private static T[] typedArray(U[] elements, int newLength, Class elementsType) { + @SuppressWarnings("unchecked") + T[] copy = (T[]) Array.newInstance(elementsType, newLength); + System.arraycopy(elements, 0, copy, 0, Math.min(elements.length, newLength)); + return copy; + } + + private static Class mostCommonType(Class c1, Class c2) { + if (c1.equals(c2)) { + return c1; + } else if (c1.isAssignableFrom(c2)) { + return c1; + } else if (c2.isAssignableFrom(c1)) { + return c2; + } + final Class s1 = c1.getSuperclass(); + final Class s2 = c2.getSuperclass(); + if (s1 == null && s2 == null) { + final List> upperTypes = Arrays.asList( + Object.class, void.class, boolean.class, char.class, + byte.class, short.class, int.class, long.class, + float.class, double.class); + for (Class type : upperTypes) { + if (c1.equals(type) && c2.equals(type)) { + return s1; + } + } + return Object.class; + } else if (s1 == null || s2 == null) { + if (c1.equals(c2)) { + return c1; + } + if (c1.isInterface() && c1.isAssignableFrom(c2)) { + return c1; + } + if (c2.isInterface() && c2.isAssignableFrom(c1)) { + return c2; + } + } + + if (s1 != null) { + return mostCommonType(s1, c2); + } else { + return mostCommonType(c1, s2); } - return result; } // } diff --git a/src/test/resources/modules/java/classes.own b/src/test/resources/modules/java/classes.own index bfd12ce..0aaecbf 100644 --- a/src/test/resources/modules/java/classes.own +++ b/src/test/resources/modules/java/classes.own @@ -38,7 +38,7 @@ def testInvokeMethodSameName() { def testNonDefaultConstructor() { StringBuilder = newClass("java.lang.StringBuilder") - sb = StringBuilder.`new`("text") + sb = new StringBuilder("text") assertEquals("text", sb.toString()) } From 1cfe654e926d49989b5a5bbeb1cf6b46df9491e4 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 4 Oct 2019 22:16:29 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D0=B5=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8=D0=B2=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=20java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annimon/ownlang/modules/java/java.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/annimon/ownlang/modules/java/java.java b/src/main/java/com/annimon/ownlang/modules/java/java.java index 890f89b..398c90e 100644 --- a/src/main/java/com/annimon/ownlang/modules/java/java.java +++ b/src/main/java/com/annimon/ownlang/modules/java/java.java @@ -153,7 +153,7 @@ public final class java implements Module { set("cast", new FunctionValue(this::cast)); } - private Value asSubclass(Value... args) { + private Value asSubclass(Value[] args) { Arguments.check(1, args.length); return new ClassValue(clazz.asSubclass( ((ClassValue)args[0]).clazz )); } @@ -208,7 +208,7 @@ public final class java implements Module { @Override public boolean containsKey(Value key) { - return getValue(object.getClass(), object, key.asString()) != null; + return get(key) != null; } @Override @@ -228,7 +228,7 @@ public final class java implements Module { } // - private Value isNull(Value... args) { + private Value isNull(Value[] args) { Arguments.checkAtLeast(1, args.length); for (Value arg : args) { if (arg.raw() == null) return NumberValue.ONE; @@ -236,24 +236,24 @@ public final class java implements Module { return NumberValue.ZERO; } - private Value newClass(Value... args) { + 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; + throw new RuntimeException("Class " + className + " not found.", ce); } } - private Value toObject(Value... args) { + 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) { + private Value toValue(Value[] args) { Arguments.check(1, args.length); if (args[0] instanceof ObjectValue) { return objectToValue( ((ObjectValue) args[0]).object ); @@ -305,7 +305,8 @@ public final class java implements Module { // skip } } - return null; + throw new RuntimeException("Constructor for " + args.length + " arguments" + + " not found or non accessible"); } private static Function methodsToFunction(Object object, List methods) { @@ -323,7 +324,9 @@ public final class java implements Module { // skip } } - return null; + final String className = (object == null ? "null" : object.getClass().getName()); + throw new RuntimeException("Method for " + args.length + " arguments" + + " not found or non accessible in " + className); }; }