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 953ae2c..398c90e 100644 --- a/src/main/java/com/annimon/ownlang/modules/java/java.java +++ b/src/main/java/com/annimon/ownlang/modules/java/java.java @@ -3,10 +3,12 @@ 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; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -102,7 +104,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; @@ -151,25 +153,22 @@ 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 )); } - 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; - } + @Override + public Value newInstance(Value[] args) { + 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)); } @@ -209,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 @@ -229,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; @@ -237,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 ); @@ -293,6 +292,22 @@ 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 + } + } + throw new RuntimeException("Constructor for " + args.length + " arguments" + + " not found or non accessible"); + } private static Function methodsToFunction(Object object, List methods) { return (args) -> { @@ -309,10 +324,12 @@ 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); }; } - + private static boolean isMatch(Value[] args, Class[] types) { for (int i = 0; i < args.length; i++) { final Value arg = args[i]; @@ -324,7 +341,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; @@ -447,7 +472,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++) { @@ -477,11 +502,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/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 diff --git a/src/test/resources/modules/java/classes.own b/src/test/resources/modules/java/classes.own index 14ddbc6..0aaecbf 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 = new StringBuilder("text") + assertEquals("text", sb.toString()) +} + def createObject() { dataClass = newClass("interop.Data")