Skip to content

Commit 0614d0f

Browse files
authored
Merge pull request #6 from aNNiMON/java-interop-fix
Java interop fixes and improvements
2 parents a5c8842 + 1cfe654 commit 0614d0f

File tree

5 files changed

+143
-34
lines changed

5 files changed

+143
-34
lines changed

src/main/java/com/annimon/ownlang/lib/ClassDeclarations.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package com.annimon.ownlang.lib;
22

3-
import com.annimon.ownlang.exceptions.UnknownFunctionException;
43
import com.annimon.ownlang.parser.ast.ClassDeclarationStatement;
5-
import java.util.HashMap;
64
import java.util.Map;
5+
import java.util.concurrent.ConcurrentHashMap;
76

87
public final class ClassDeclarations {
98

109
private static final Map<String, ClassDeclarationStatement> declarations;
1110
static {
12-
declarations = new HashMap<>();
11+
declarations = new ConcurrentHashMap<>();
1312
}
1413

1514
private ClassDeclarations() { }
@@ -22,12 +21,7 @@ public static Map<String, ClassDeclarationStatement> getAll() {
2221
return declarations;
2322
}
2423

25-
public static boolean isExists(String key) {
26-
return declarations.containsKey(key);
27-
}
28-
2924
public static ClassDeclarationStatement get(String key) {
30-
if (!isExists(key)) throw new UnknownFunctionException(key);
3125
return declarations.get(key);
3226
}
3327

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.annimon.ownlang.lib;
2+
3+
/**
4+
* Interface for values that supports creating instances with `new` keyword.
5+
*/
6+
public interface Instantiable {
7+
8+
Value newInstance(Value[] args);
9+
}

src/main/java/com/annimon/ownlang/modules/java/java.java

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import com.annimon.ownlang.lib.*;
44
import com.annimon.ownlang.modules.Module;
55
import java.lang.reflect.Array;
6+
import java.lang.reflect.Constructor;
67
import java.lang.reflect.Field;
78
import java.lang.reflect.InvocationTargetException;
89
import java.lang.reflect.Method;
910
import java.util.ArrayList;
11+
import java.util.Arrays;
1012
import java.util.List;
1113

1214
/**
@@ -102,7 +104,7 @@ public String toString() {
102104
}
103105
}
104106

105-
private static class ClassValue extends MapValue {
107+
private static class ClassValue extends MapValue implements Instantiable {
106108

107109
public static Value classOrNull(Class<?> clazz) {
108110
if (clazz == null) return NULL;
@@ -151,25 +153,22 @@ private void init(Class<?> clazz) {
151153
set("cast", new FunctionValue(this::cast));
152154
}
153155

154-
private Value asSubclass(Value... args) {
156+
private Value asSubclass(Value[] args) {
155157
Arguments.check(1, args.length);
156158
return new ClassValue(clazz.asSubclass( ((ClassValue)args[0]).clazz ));
157159
}
158160

159-
private Value isAssignableFrom(Value... args) {
161+
private Value isAssignableFrom(Value[] args) {
160162
Arguments.check(1, args.length);
161163
return NumberValue.fromBoolean(clazz.isAssignableFrom( ((ClassValue)args[0]).clazz ));
162164
}
163165

164-
private Value newInstance(Value... args) {
165-
try {
166-
return new ObjectValue(clazz.newInstance());
167-
} catch (InstantiationException | IllegalAccessException ex) {
168-
return NULL;
169-
}
166+
@Override
167+
public Value newInstance(Value[] args) {
168+
return findConstructorAndInstantiate(args, clazz.getConstructors());
170169
}
171170

172-
private Value cast(Value... args) {
171+
private Value cast(Value[] args) {
173172
Arguments.check(1, args.length);
174173
return objectToValue(clazz, clazz.cast(((ObjectValue)args[0]).object));
175174
}
@@ -209,7 +208,7 @@ public ObjectValue(Object object) {
209208

210209
@Override
211210
public boolean containsKey(Value key) {
212-
return getValue(object.getClass(), object, key.asString()) != null;
211+
return get(key) != null;
213212
}
214213

215214
@Override
@@ -229,32 +228,32 @@ public String toString() {
229228
}
230229
//</editor-fold>
231230

232-
private Value isNull(Value... args) {
231+
private Value isNull(Value[] args) {
233232
Arguments.checkAtLeast(1, args.length);
234233
for (Value arg : args) {
235234
if (arg.raw() == null) return NumberValue.ONE;
236235
}
237236
return NumberValue.ZERO;
238237
}
239238

240-
private Value newClass(Value... args) {
239+
private Value newClass(Value[] args) {
241240
Arguments.check(1, args.length);
242241

243242
final String className = args[0].asString();
244243
try {
245244
return new ClassValue(Class.forName(className));
246245
} catch (ClassNotFoundException ce) {
247-
return NULL;
246+
throw new RuntimeException("Class " + className + " not found.", ce);
248247
}
249248
}
250249

251-
private Value toObject(Value... args) {
250+
private Value toObject(Value[] args) {
252251
Arguments.check(1, args.length);
253252
if (args[0] == NULL) return NULL;
254253
return new ObjectValue(valueToObject(args[0]));
255254
}
256255

257-
private Value toValue(Value... args) {
256+
private Value toValue(Value[] args) {
258257
Arguments.check(1, args.length);
259258
if (args[0] instanceof ObjectValue) {
260259
return objectToValue( ((ObjectValue) args[0]).object );
@@ -293,6 +292,22 @@ private static Value getValue(Class<?> clazz, Object object, String key) {
293292

294293
return NULL;
295294
}
295+
296+
private static Value findConstructorAndInstantiate(Value[] args, Constructor<?>[] ctors) {
297+
for (Constructor<?> ctor : ctors) {
298+
if (ctor.getParameterCount() != args.length) continue;
299+
if (!isMatch(args, ctor.getParameterTypes())) continue;
300+
try {
301+
final Object result = ctor.newInstance(valuesToObjects(args));
302+
return new ObjectValue(result);
303+
} catch (InstantiationException | IllegalAccessException
304+
| IllegalArgumentException | InvocationTargetException ex) {
305+
// skip
306+
}
307+
}
308+
throw new RuntimeException("Constructor for " + args.length + " arguments"
309+
+ " not found or non accessible");
310+
}
296311

297312
private static Function methodsToFunction(Object object, List<Method> methods) {
298313
return (args) -> {
@@ -309,10 +324,12 @@ private static Function methodsToFunction(Object object, List<Method> methods) {
309324
// skip
310325
}
311326
}
312-
return null;
327+
final String className = (object == null ? "null" : object.getClass().getName());
328+
throw new RuntimeException("Method for " + args.length + " arguments"
329+
+ " not found or non accessible in " + className);
313330
};
314331
}
315-
332+
316333
private static boolean isMatch(Value[] args, Class<?>[] types) {
317334
for (int i = 0; i < args.length; i++) {
318335
final Value arg = args[i];
@@ -324,7 +341,15 @@ private static boolean isMatch(Value[] args, Class<?>[] types) {
324341
boolean assignable = unboxed != null;
325342
final Object object = valueToObject(arg);
326343
assignable = assignable && (object != null);
327-
assignable = assignable && (unboxed.isAssignableFrom(object.getClass()));
344+
if (assignable && unboxed.isArray() && object.getClass().isArray()) {
345+
final Class<?> uComponentType = unboxed.getComponentType();
346+
final Class<?> oComponentType = object.getClass().getComponentType();
347+
assignable = assignable && (uComponentType != null);
348+
assignable = assignable && (oComponentType != null);
349+
assignable = assignable && (uComponentType.isAssignableFrom(oComponentType));
350+
} else {
351+
assignable = assignable && (unboxed.isAssignableFrom(object.getClass()));
352+
}
328353
if (assignable) continue;
329354

330355
return false;
@@ -447,7 +472,7 @@ private static Value arrayToValue(Class<?> clazz, Object o) {
447472
}
448473
return result;
449474
}
450-
475+
451476
private static Object[] valuesToObjects(Value[] args) {
452477
Object[] result = new Object[args.length];
453478
for (int i = 0; i < args.length; i++) {
@@ -477,11 +502,72 @@ private static Object valueToObject(Value value) {
477502

478503
private static Object arrayToObject(ArrayValue value) {
479504
final int size = value.size();
480-
final Object[] result = new Object[size];
505+
final Object[] array = new Object[size];
506+
if (size == 0) {
507+
return array;
508+
}
509+
510+
Class<?> elementsType = null;
481511
for (int i = 0; i < size; i++) {
482-
result[i] = valueToObject(value.get(i));
512+
array[i] = valueToObject(value.get(i));
513+
if (i == 0) {
514+
elementsType = array[0].getClass();
515+
} else {
516+
elementsType = mostCommonType(elementsType, array[i].getClass());
517+
}
518+
}
519+
520+
if (elementsType.equals(Object[].class)) {
521+
return array;
522+
}
523+
return typedArray(array, size, elementsType);
524+
}
525+
526+
private static <T, U> T[] typedArray(U[] elements, int newLength, Class<?> elementsType) {
527+
@SuppressWarnings("unchecked")
528+
T[] copy = (T[]) Array.newInstance(elementsType, newLength);
529+
System.arraycopy(elements, 0, copy, 0, Math.min(elements.length, newLength));
530+
return copy;
531+
}
532+
533+
private static Class<?> mostCommonType(Class<?> c1, Class<?> c2) {
534+
if (c1.equals(c2)) {
535+
return c1;
536+
} else if (c1.isAssignableFrom(c2)) {
537+
return c1;
538+
} else if (c2.isAssignableFrom(c1)) {
539+
return c2;
540+
}
541+
final Class<?> s1 = c1.getSuperclass();
542+
final Class<?> s2 = c2.getSuperclass();
543+
if (s1 == null && s2 == null) {
544+
final List<Class<?>> upperTypes = Arrays.asList(
545+
Object.class, void.class, boolean.class, char.class,
546+
byte.class, short.class, int.class, long.class,
547+
float.class, double.class);
548+
for (Class<?> type : upperTypes) {
549+
if (c1.equals(type) && c2.equals(type)) {
550+
return s1;
551+
}
552+
}
553+
return Object.class;
554+
} else if (s1 == null || s2 == null) {
555+
if (c1.equals(c2)) {
556+
return c1;
557+
}
558+
if (c1.isInterface() && c1.isAssignableFrom(c2)) {
559+
return c1;
560+
}
561+
if (c2.isInterface() && c2.isAssignableFrom(c1)) {
562+
return c2;
563+
}
564+
}
565+
566+
if (s1 != null) {
567+
return mostCommonType(s1, c2);
568+
} else {
569+
return mostCommonType(c1, s2);
483570
}
484-
return result;
485571
}
486572
//</editor-fold>
487573
}

src/main/java/com/annimon/ownlang/parser/ast/ObjectCreationExpression.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.annimon.ownlang.parser.ast;
22

3+
import com.annimon.ownlang.exceptions.UnknownClassException;
34
import com.annimon.ownlang.lib.*;
45
import java.util.Iterator;
56
import java.util.List;
@@ -17,6 +18,16 @@ public ObjectCreationExpression(String className, List<Expression> constructorAr
1718
@Override
1819
public Value eval() {
1920
final ClassDeclarationStatement cd = ClassDeclarations.get(className);
21+
if (cd == null) {
22+
// Is Instantiable?
23+
if (Variables.isExists(className)) {
24+
final Value variable = Variables.get(className);
25+
if (variable instanceof Instantiable) {
26+
return ((Instantiable) variable).newInstance(ctorArgs());
27+
}
28+
}
29+
throw new UnknownClassException(className);
30+
}
2031

2132
// Create an instance and put evaluated fields with method declarations
2233
final ClassInstanceValue instance = new ClassInstanceValue(className);
@@ -30,14 +41,17 @@ public Value eval() {
3041
}
3142

3243
// Call a constructor
44+
instance.callConstructor(ctorArgs());
45+
return instance;
46+
}
47+
48+
private Value[] ctorArgs() {
3349
final int argsSize = constructorArguments.size();
3450
final Value[] ctorArgs = new Value[argsSize];
3551
for (int i = 0; i < argsSize; i++) {
3652
ctorArgs[i] = constructorArguments.get(i).eval();
3753
}
38-
instance.callConstructor(ctorArgs);
39-
40-
return instance;
54+
return ctorArgs;
4155
}
4256

4357
@Override

src/test/resources/modules/java/classes.own

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ def testInvokeMethodSameName() {
3636
assertEquals("text", data.getText())
3737
}
3838

39+
def testNonDefaultConstructor() {
40+
StringBuilder = newClass("java.lang.StringBuilder")
41+
sb = new StringBuilder("text")
42+
assertEquals("text", sb.toString())
43+
}
44+
3945

4046
def createObject() {
4147
dataClass = newClass("interop.Data")

0 commit comments

Comments
 (0)