问题
Ok, so r.js can run on Rhino. Which is great.
To do the stuff it needs to do.
On rhino it basically uses java.io.File
, java.io.FileOutputStream
and java.io.FileInputStream
to achieve the filesystem modifications that it needs to do.
(Background: I am working on delivering a better development experience for Maven based Java/Javascript developers. Being Maven, there is the power of convention and the power of being opinionated. You can see the progress at jszip.org.)
So what I want to do is have the on-disk structure appear by magic as a virtual file system.
So on disk we will have a structure like so:
/
/module1/src/main/js/controllers/controller.js
/module2/src/main/js/models/model.js
/module3/src/main/js/views/view.js
/webapp/src/build/js/profile.js
/webapp/src/main/js/main.js
/webapp/src/main/webapp/index.html
The /webapp/src/build/js/profile.js
should look something like this:
({
appDir: "src",
baseUrl:".",
dir: "target",
optimize: "closure",
modules:[
{
name:"main"
}
]
})
Such that
when r.js asks for
new File("src/main.js")
I will actually give itnew File("/webapp/src/main/js/main.js")
when it asks for
new File("profile.js")
I will give itnew File("/webapp/src/build/js/profile.js")
when it asks for
new File("controllers/controller.js")
I will give itnew File("/module1/src/main/js/controllers/controller.js")
when it asks for
new File("target")
I will give itnew File("/webapp/target/webapp-1.0-SNAPSHOT")
.
I have no issue writing the three mock classes required, i.e. the ones to use in place of java.io.File
, java.io.FileInputStream
and java.io.FileOutputStream
,
Some questions such as this have answers that point to things like ClassShutter, which I can see I could use like this:
context.setClassShutter(new ClassShutter() {
public boolean visibleToScripts(String fullClassName) {
if (File.class.getName().equals(fullClassName)) return false;
if (FileOutputStream.class.getName().equals(fullClassName)) return false;
if (FileInputStream.class.getName().equals(fullClassName)) return false;
return true;
}
});
To hide the original implementations.
The problem is then getting Rhino to resolve the sandboxed equivalents... I keep on getting
TypeError: [JavaPackage java.io.File] is not a function, it is object.
Even if I prefix the call with a prior execution of java.io.File = org.jszip.rhino.SandboxFile
map my sandboxed implementation over the now missing java.io.File
I could even consider using search and replace on the loaded r.js
file just prior to compiling it... but I feel there must be a better way.
Does anyone have any hints?
回答1:
Ok, after much experimentation, this seems to be the way to do this:
Scriptable scope = context.newObject(global);
scope.setPrototype(global);
scope.setParentScope(null);
NativeJavaTopPackage $packages = (NativeJavaTopPackage) global.get("Packages");
NativeJavaPackage $java = (NativeJavaPackage) $packages.get("java");
NativeJavaPackage $java_io = (NativeJavaPackage) $java.get("io");
ProxyNativeJavaPackage proxy$java = new ProxyNativeJavaPackage($java);
ProxyNativeJavaPackage proxy$java_io = new ProxyNativeJavaPackage($java_io);
proxy$java_io.put("File", scope, get(scope, "Packages." + PseudoFile.class.getName()));
proxy$java_io.put("FileInputStream", scope,
get(scope, "Packages." + PseudoFileInputStream.class.getName()));
proxy$java_io.put("FileOutputStream", scope,
get(scope, "Packages." + PseudoFileOutputStream.class.getName()));
proxy$java.put("io", scope, proxy$java_io);
scope.put("java", scope, proxy$java);
There is a helper method:
private static Object get(Scriptable scope, String name) {
Scriptable cur = scope;
for (String part : StringUtils.split(name, ".")) {
Object next = cur.get(part, scope);
if (next instanceof Scriptable) {
cur = (Scriptable) next;
} else {
return null;
}
}
return cur;
}
And where ProxyNativeJavaPackage
is something like
public class ProxyNativeJavaPackage extends ScriptableObject implements Serializable {
static final long serialVersionUID = 1L;
protected final NativeJavaPackage delegate;
private final Map<String, Object> mutations = new HashMap<String, Object>();
public ProxyNativeJavaPackage(NativeJavaPackage delegate) {
delegate.getClass();
this.delegate = delegate;
}
@Override
public String getClassName() {
return delegate.getClassName();
}
@Override
public boolean has(String id, Scriptable start) {
return mutations.containsKey(id) ? mutations.get(id) != null : delegate.has(id, start);
}
@Override
public boolean has(int index, Scriptable start) {
return delegate.has(index, start);
}
@Override
public void put(String id, Scriptable start, Object value) {
mutations.put(id, value);
}
@Override
public void put(int index, Scriptable start, Object value) {
delegate.put(index, start, value);
}
@Override
public Object get(String id, Scriptable start) {
if (mutations.containsKey(id)) {
return mutations.get(id);
}
return delegate.get(id, start);
}
@Override
public Object get(int index, Scriptable start) {
return delegate.get(index, start);
}
@Override
public Object getDefaultValue(Class<?> ignored) {
return toString();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ProxyNativeJavaPackage) {
ProxyNativeJavaPackage that = (ProxyNativeJavaPackage) obj;
return delegate.equals(that.delegate) && mutations.equals(that.mutations);
}
return false;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}
That still leaves the original classes at Packages.java.io.File
etc, but for the r.js
requirement this is sufficient, and it should be possible for others to extend this trick to the general case.
来源:https://stackoverflow.com/questions/13569734/how-can-i-provide-a-pseudo-file-system-for-r-js