简体   繁体   中英

How can I provide a pseudo file system for r.js?

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 it new File("/webapp/src/main/js/main.js")

  • when it asks for new File("profile.js") I will give it new File("/webapp/src/build/js/profile.js")

  • when it asks for new File("controllers/controller.js") I will give it new File("/module1/src/main/js/controllers/controller.js")

  • when it asks for new File("target") I will give it new File("/webapp/target/webapp-1.0-SNAPSHOT") .

I have no issue writing the three mock classes required, ie 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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM