Skip to content

Commit 2f2bbef

Browse files
authored
Merge pull request #27 from DensyDev/feat/rhino-modules
feat: rhino modules
2 parents 2d39744 + 40d93da commit 2f2bbef

13 files changed

Lines changed: 793 additions & 8 deletions

File tree

core/src/main/java/org/densy/scriptify/core/script/function/definition/ScriptFunctionArgumentDefinitionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package org.densy.scriptify.core.script.function.definition;
22

3-
import org.densy.scriptify.api.script.function.definition.ScriptFunctionArgumentDefinition;
43
import lombok.AllArgsConstructor;
54
import lombok.Getter;
65
import lombok.ToString;
6+
import org.densy.scriptify.api.script.function.definition.ScriptFunctionArgumentDefinition;
77

88
@Getter
99
@AllArgsConstructor

script-js-rhino/src/main/java/org/densy/scriptify/js/rhino/script/JsScript.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
import org.densy.scriptify.api.script.security.ScriptSecurityManager;
1212
import org.densy.scriptify.core.script.constant.StandardConstantManager;
1313
import org.densy.scriptify.core.script.function.StandardFunctionManager;
14+
import org.densy.scriptify.core.script.module.export.ScriptConstantExport;
15+
import org.densy.scriptify.core.script.module.export.ScriptFunctionDefinitionExport;
1416
import org.densy.scriptify.core.script.security.StandardSecurityManager;
17+
import org.densy.scriptify.js.rhino.script.module.RhinoModuleManager;
18+
import org.densy.scriptify.js.rhino.script.module.RhinoModuleSourceTransformer;
1519
import org.mozilla.javascript.Context;
1620
import org.mozilla.javascript.ScriptableObject;
1721

@@ -22,6 +26,7 @@
2226
public class JsScript implements Script<Object> {
2327

2428
private final ScriptSecurityManager securityManager = new StandardSecurityManager();
29+
private final RhinoModuleManager moduleManager = new RhinoModuleManager(this);
2530
private ScriptFunctionManager functionManager = new StandardFunctionManager();
2631
private ScriptConstantManager constantManager = new StandardConstantManager();
2732
private final List<String> extraScript = new ArrayList<>();
@@ -33,7 +38,7 @@ public ScriptSecurityManager getSecurityManager() {
3338

3439
@Override
3540
public ScriptModuleManager getModuleManager() {
36-
throw new UnsupportedOperationException("Rhino does not support a module system.");
41+
return moduleManager;
3742
}
3843

3944
@Override
@@ -65,7 +70,8 @@ public void addExtraScript(String script) {
6570
public CompiledScript<Object> compile(String script) throws ScriptException {
6671
try {
6772
Context context = Context.enter();
68-
context.setWrapFactory(new JsWrapFactory());
73+
context.setLanguageVersion(Context.VERSION_ES6);
74+
context.setWrapFactory(new JsWrapFactory(moduleManager.getScriptAccess()));
6975

7076
ScriptableObject scope = context.initStandardObjects();
7177

@@ -76,12 +82,13 @@ public CompiledScript<Object> compile(String script) throws ScriptException {
7682
}
7783

7884
for (ScriptFunctionDefinition definition : functionManager.getFunctions().values()) {
79-
scope.put(definition.getFunction().getName(), scope, new JsFunction(this, definition));
85+
moduleManager.getGlobalModule().export(new ScriptFunctionDefinitionExport(definition));
8086
}
8187

8288
for (ScriptConstant constant : constantManager.getConstants().values()) {
83-
ScriptableObject.putConstProperty(scope, constant.getName(), constant.getValue());
89+
moduleManager.getGlobalModule().export(new ScriptConstantExport(constant));
8490
}
91+
moduleManager.applyTo(context, scope);
8592

8693
// Building full script including extra script code
8794
StringBuilder fullScript = new StringBuilder();
@@ -90,7 +97,12 @@ public CompiledScript<Object> compile(String script) throws ScriptException {
9097
}
9198
fullScript.append(script);
9299

93-
var compiled = context.compileString(fullScript.toString(), "script", 1, null);
100+
var compiled = context.compileString(
101+
RhinoModuleSourceTransformer.transformScript(fullScript.toString()),
102+
"script",
103+
1,
104+
null
105+
);
94106
return new JsCompiledScript(context, scope, compiled);
95107
} catch (Exception e) {
96108
throw new ScriptException(e);
Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
11
package org.densy.scriptify.js.rhino.script;
22

33
import org.densy.scriptify.api.script.ScriptObject;
4+
import org.densy.scriptify.api.script.module.export.access.ScriptAccess;
5+
import org.densy.scriptify.js.rhino.script.access.RestrictedNativeJavaClass;
6+
import org.densy.scriptify.js.rhino.script.access.RestrictedNativeJavaObject;
7+
import org.densy.scriptify.js.rhino.script.access.RhinoScriptAccessSupport;
48
import org.mozilla.javascript.Context;
59
import org.mozilla.javascript.Scriptable;
610
import org.mozilla.javascript.WrapFactory;
711

812
public class JsWrapFactory extends WrapFactory {
913

10-
public JsWrapFactory() {
14+
private final ScriptAccess scriptAccess;
15+
16+
public JsWrapFactory(ScriptAccess scriptAccess) {
17+
this.scriptAccess = scriptAccess;
1118
this.setJavaPrimitiveWrap(false);
1219
}
1320

1421
@Override
1522
public Object wrap(Context context, Scriptable scope, Object object, Class<?> staticType) {
1623
// Convert the ScriptObject class to the value it contains
1724
if (object instanceof ScriptObject scriptObject) {
18-
return Context.javaToJS(scriptObject.getValue(), scope);
25+
return super.wrap(context, scope, scriptObject.getValue(), staticType);
1926
}
2027
return super.wrap(context, scope, object, staticType);
2128
}
29+
30+
@Override
31+
public Scriptable wrapAsJavaObject(Context context, Scriptable scope, Object javaObject, Class<?> staticType) {
32+
if (RhinoScriptAccessSupport.isExplicit(scriptAccess)) {
33+
return new RestrictedNativeJavaObject(scope, javaObject, staticType);
34+
}
35+
return super.wrapAsJavaObject(context, scope, javaObject, staticType);
36+
}
37+
38+
@Override
39+
public Scriptable wrapJavaClass(Context context, Scriptable scope, Class<?> javaClass) {
40+
if (RhinoScriptAccessSupport.isExplicit(scriptAccess)) {
41+
return new RestrictedNativeJavaClass(scope, javaClass);
42+
}
43+
return super.wrapJavaClass(context, scope, javaClass);
44+
}
2245
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.densy.scriptify.js.rhino.script.access;
2+
3+
import org.mozilla.javascript.BaseFunction;
4+
import org.mozilla.javascript.Context;
5+
import org.mozilla.javascript.Scriptable;
6+
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Method;
9+
10+
final class RestrictedJavaMethodFunction extends BaseFunction {
11+
12+
private final Object target;
13+
private final Class<?> type;
14+
private final String name;
15+
private final boolean staticOnly;
16+
17+
RestrictedJavaMethodFunction(Scriptable scope, Object target, Class<?> type, String name, boolean staticOnly) {
18+
this.target = target;
19+
this.type = type;
20+
this.name = name;
21+
this.staticOnly = staticOnly;
22+
this.setParentScope(scope);
23+
this.setPrototype(getFunctionPrototype(scope));
24+
}
25+
26+
@Override
27+
public Object call(Context context, Scriptable scope, Scriptable thisObj, Object[] args) {
28+
for (Method method : type.getMethods()) {
29+
if (!isCandidate(method)) {
30+
continue;
31+
}
32+
33+
Object[] converted = RhinoScriptAccessSupport.convertArguments(
34+
context,
35+
args,
36+
method.getParameterTypes(),
37+
method.isVarArgs()
38+
);
39+
if (converted == null) {
40+
continue;
41+
}
42+
43+
try {
44+
Object result = method.invoke(RhinoScriptAccessSupport.isStatic(method) ? null : target, converted);
45+
return context.getWrapFactory().wrap(context, scope, result, method.getReturnType());
46+
} catch (IllegalArgumentException ignored) {
47+
// Try the next overload.
48+
} catch (IllegalAccessException | InvocationTargetException e) {
49+
throw Context.throwAsScriptRuntimeEx(e);
50+
}
51+
}
52+
53+
throw Context.reportRuntimeError("No exported method '" + name + "' matches provided arguments");
54+
}
55+
56+
private boolean isCandidate(Method method) {
57+
return method.getName().equals(name)
58+
&& RhinoScriptAccessSupport.isExported(method)
59+
&& (!staticOnly || RhinoScriptAccessSupport.isStatic(method))
60+
&& (staticOnly || !RhinoScriptAccessSupport.isStatic(method));
61+
}
62+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.densy.scriptify.js.rhino.script.access;
2+
3+
import org.mozilla.javascript.Context;
4+
import org.mozilla.javascript.NativeJavaClass;
5+
import org.mozilla.javascript.Scriptable;
6+
7+
import java.lang.reflect.Constructor;
8+
import java.lang.reflect.Field;
9+
import java.lang.reflect.InvocationTargetException;
10+
11+
public final class RestrictedNativeJavaClass extends NativeJavaClass {
12+
13+
public RestrictedNativeJavaClass(Scriptable scope, Class<?> javaClass) {
14+
super(scope, javaClass);
15+
}
16+
17+
@Override
18+
public boolean has(String name, Scriptable start) {
19+
Class<?> type = getClassObject();
20+
return RhinoScriptAccessSupport.findExportedField(type, name, true) != null
21+
|| RhinoScriptAccessSupport.hasExportedMethod(type, name, true);
22+
}
23+
24+
@Override
25+
public Object get(String name, Scriptable start) {
26+
Class<?> type = getClassObject();
27+
Field field = RhinoScriptAccessSupport.findExportedField(type, name, true);
28+
if (field != null) {
29+
try {
30+
return Context.getCurrentContext().getWrapFactory().wrap(
31+
Context.getCurrentContext(),
32+
getParentScope(),
33+
field.get(null),
34+
field.getType()
35+
);
36+
} catch (IllegalAccessException e) {
37+
throw Context.throwAsScriptRuntimeEx(e);
38+
}
39+
}
40+
41+
if (RhinoScriptAccessSupport.hasExportedMethod(type, name, true)) {
42+
return new RestrictedJavaMethodFunction(getParentScope(), null, type, name, true);
43+
}
44+
45+
return Scriptable.NOT_FOUND;
46+
}
47+
48+
@Override
49+
public void put(String name, Scriptable start, Object value) {
50+
Field field = RhinoScriptAccessSupport.findExportedField(getClassObject(), name, true);
51+
if (field == null) {
52+
throw Context.reportRuntimeError("Java static member is not exported: " + name);
53+
}
54+
try {
55+
field.set(null, Context.jsToJava(value, field.getType()));
56+
} catch (IllegalAccessException e) {
57+
throw Context.throwAsScriptRuntimeEx(e);
58+
}
59+
}
60+
61+
@Override
62+
public Object[] getIds() {
63+
return RhinoScriptAccessSupport.getExportedClassMemberNames(getClassObject()).toArray();
64+
}
65+
66+
@Override
67+
public Scriptable construct(Context context, Scriptable scope, Object[] args) {
68+
for (Constructor<?> constructor : RhinoScriptAccessSupport.getExportedConstructors(getClassObject())) {
69+
Object[] converted = RhinoScriptAccessSupport.convertArguments(
70+
context,
71+
args,
72+
constructor.getParameterTypes(),
73+
constructor.isVarArgs()
74+
);
75+
if (converted == null) {
76+
continue;
77+
}
78+
79+
try {
80+
Object instance = constructor.newInstance(converted);
81+
return context.getWrapFactory().wrapNewObject(context, scope, instance);
82+
} catch (IllegalArgumentException ignored) {
83+
// Try the next overload.
84+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
85+
throw Context.throwAsScriptRuntimeEx(e);
86+
}
87+
}
88+
89+
throw Context.reportRuntimeError("No exported constructor matches provided arguments for " + getClassObject().getName());
90+
}
91+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.densy.scriptify.js.rhino.script.access;
2+
3+
import org.mozilla.javascript.Context;
4+
import org.mozilla.javascript.NativeJavaObject;
5+
import org.mozilla.javascript.Scriptable;
6+
7+
import java.lang.reflect.Field;
8+
9+
public final class RestrictedNativeJavaObject extends NativeJavaObject {
10+
11+
public RestrictedNativeJavaObject(Scriptable scope, Object javaObject, Class<?> staticType) {
12+
super(scope, javaObject, staticType);
13+
}
14+
15+
@Override
16+
public boolean has(String name, Scriptable start) {
17+
Class<?> type = getWrappedType();
18+
return RhinoScriptAccessSupport.findExportedField(type, name, false) != null
19+
|| RhinoScriptAccessSupport.hasExportedMethod(type, name, false);
20+
}
21+
22+
@Override
23+
public Object get(String name, Scriptable start) {
24+
Class<?> type = getWrappedType();
25+
Field field = RhinoScriptAccessSupport.findExportedField(type, name, false);
26+
if (field != null) {
27+
try {
28+
Object value = field.get(unwrap());
29+
return Context.getCurrentContext().getWrapFactory().wrap(
30+
Context.getCurrentContext(),
31+
getParentScope(),
32+
value,
33+
field.getType()
34+
);
35+
} catch (IllegalAccessException e) {
36+
throw Context.throwAsScriptRuntimeEx(e);
37+
}
38+
}
39+
40+
if (RhinoScriptAccessSupport.hasExportedMethod(type, name, false)) {
41+
return new RestrictedJavaMethodFunction(getParentScope(), unwrap(), type, name, false);
42+
}
43+
44+
return Scriptable.NOT_FOUND;
45+
}
46+
47+
@Override
48+
public void put(String name, Scriptable start, Object value) {
49+
Field field = RhinoScriptAccessSupport.findExportedField(getWrappedType(), name, false);
50+
if (field == null) {
51+
throw Context.reportRuntimeError("Java member is not exported: " + name);
52+
}
53+
try {
54+
field.set(unwrap(), Context.jsToJava(value, field.getType()));
55+
} catch (IllegalAccessException e) {
56+
throw Context.throwAsScriptRuntimeEx(e);
57+
}
58+
}
59+
60+
@Override
61+
public Object[] getIds() {
62+
return RhinoScriptAccessSupport.getExportedInstanceMemberNames(getWrappedType()).toArray();
63+
}
64+
65+
private Class<?> getWrappedType() {
66+
Object wrapped = unwrap();
67+
if (staticType != null && staticType != Object.class) {
68+
return staticType;
69+
}
70+
return wrapped.getClass();
71+
}
72+
}

0 commit comments

Comments
 (0)