/*
* Copyright 2012 Jason Miller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jj.jasmine;
import static jj.server.ServerLocation.*;
import java.io.IOException;
import javax.inject.Inject;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import jj.resource.Location;
import jj.resource.NoSuchResourceException;
import jj.resource.PathResolver;
import jj.resource.ResourceFinder;
import jj.resource.ResourceLoaded;
import jj.script.AbstractScriptEnvironment;
import jj.script.Global;
import jj.script.RhinoContext;
import jj.script.module.RootScriptEnvironment;
import jj.script.module.ScriptResource;
import jj.util.SHA1Helper;
/**
* <p>
* The central object of the Jasmine integration. Coordinates a number of filesystem
* resources, provides a reload point in the resource system, and provides an
* environment for the script execution
*
* @author jason
*
*/
public class JasmineScriptEnvironment extends AbstractScriptEnvironment<ResourceLoaded> implements RootScriptEnvironment<ResourceLoaded> {
private static final String JASMINE = "jasmine";
static final String JASMINE_JS = JASMINE + ".js";
static final String JASMINE_BOOT_JS = JASMINE + "-boot.js";
static final String JASMINE_RUN_JS = JASMINE + "-run.js";
private final ScriptableObject scope;
private final ScriptableObject global;
private final String sha1;
private final ScriptResource jasmine;
private final ScriptResource target;
private final ScriptResource spec;
private final ScriptResource boot;
private final ScriptResource runner;
private final ResourceLoaded resourceLoaded;
private final Location specLocation;
@Inject
JasmineScriptEnvironment(
final Dependencies dependencies,
final @Global ScriptableObject global,
final ResourceFinder resourceFinder,
final PathResolver pathResolver,
final ResourceLoaded resourceLoaded // the original event that caused us to get loaded
) {
super(dependencies);
assert resourceLoaded.type() == ScriptResource.class : "cannot run ";
this.resourceLoaded = resourceLoaded;
this.global = global;
this.specLocation = pathResolver.specLocationFor(resourceLoaded.base());
// we need some scripts to be happy
target = resourceFinder.loadResource(resourceLoaded.identifier());
spec = resourceFinder.loadResource(ScriptResource.class, specLocation, name());
// target is unlikely to be null, but maybe it got deleted while we were getting created
// if spec is null, we have nothing to do
if (target == null || spec == null) {
throw new NoSuchResourceException(JasmineScriptEnvironment.class, name());
}
// if we have a reason to load stuff, load stuff!
jasmine = resourceFinder.loadResource(ScriptResource.class, Assets, JASMINE_JS);
boot = resourceFinder.loadResource(ScriptResource.class, Assets, JASMINE_BOOT_JS);
runner = resourceFinder.loadResource(ScriptResource.class, Assets, JASMINE_RUN_JS);
assert jasmine != null : "can't find the jasmine script";
assert boot != null : "can't find the jasmine-boot script";
assert runner != null : "can't find the jasmine-run script";
// okay, we have all the things we need, now we can bother finishing up
sha1 = SHA1Helper.keyFor(target.sha1(), spec.sha1(), jasmine.sha1(), boot.sha1(), runner.sha1());
scope = scope(global);
dependencies();
}
private ScriptableObject scope(ScriptableObject global) {
// need to configure different require to allow it to be mocked.
ScriptableObject scope = configureModuleObjects(JASMINE, createChainedScope(global), "$$realRequire");
// also need different inject.
configureInjectFunction(configureTimers(scope), "$$realInject");
// and we need to pre-execute the jasmine script into our scope, because requiring it
// makes it use the wrong global, and therefore timers can't be simulated
try (RhinoContext context = contextProvider.get()) {
context.executeScript(jasmine.script(), scope);
}
return scope;
}
private void dependencies() {
target.addDependent(this);
spec.addDependent(this);
jasmine.addDependent(this);
boot.addDependent(this);
runner.addDependent(this);
}
@Override
public ResourceLoaded creationArg() {
return resourceLoaded;
}
@Override
public Scriptable scope() {
return scope;
}
@Override
public ScriptableObject global() {
return global;
}
@Override
public Script script() {
return boot.script();
}
@Override
public Location moduleLocation() {
return resourceLoaded.base().and(specLocation);
}
ScriptResource spec() {
return spec;
}
Script specScript() {
return spec.script();
}
ScriptResource target() {
return target;
}
Script targetScript() {
return target.script();
}
Script runnerScript() {
return runner.script();
}
@Override
public String scriptName() {
return JASMINE;
}
@Override
public String sha1() {
return sha1;
}
@Override
public boolean needsReplacing() throws IOException {
// driven by the underlying scripts
return false;
}
@Override
protected boolean removeOnReload() {
// and with this, we magically rerun any time there
// are changes to any script we use
return false;
}
}