/*
* Copyright 2002-2008 the original author or authors.
*
* 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 org.springframework.richclient.script;
import java.awt.BorderLayout;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.richclient.application.View;
import org.springframework.richclient.application.support.AbstractView;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link View} implementation that uses {@link ScriptEngine} to build its control.
*
* @author Peter De Bruycker
*/
public class ScriptedView extends AbstractView implements InitializingBean {
private Resource script;
private String engineName;
private Map<String, Object> scriptBindings;
private String viewBindingName;
private String containerBindingName;
protected JComponent createControl() {
JPanel container = new JPanel(new BorderLayout());
ScriptEngine engine = createScriptEngine();
Bindings bindings = engine.createBindings();
populateBindings(bindings, container);
ScriptContext context = new SimpleScriptContext();
// Bug workaround
context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
context.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
engine.setContext(context);
try {
engine.eval(new InputStreamReader(script.getInputStream()));
}
catch (ScriptException e) {
throw new ScriptExecutionException("error running script", e);
}
catch (IOException e) {
throw new ScriptIOException("error reading script", e);
}
return container;
}
/**
* Creates the <code>ScriptEngine</code>, by using the {@link #engineName} if provided. If no engine name is set,
* the extension of the file name of the {@link #script} is used.
*
* @return the <code>ScriptEngine</code>
*
* @see ScriptEngineManager#getEngineByName(String)
* @see ScriptEngineManager#getEngineByExtension(String)
*/
protected ScriptEngine createScriptEngine() {
ScriptEngineManager manager = new ScriptEngineManager(getClass().getClassLoader());
if (StringUtils.hasText(engineName)) {
return manager.getEngineByName(engineName);
}
return manager.getEngineByExtension(getExtension(script.getFilename()));
}
private String getExtension(String fileName) {
return fileName.substring(fileName.lastIndexOf('.') + 1);
}
/**
* Populates the bindings that will be passed to the script.
* <p>
* If the {@link #containerBindingName} is set, the container instance will be included in the bindings.
* <p>
* If the {@link #viewBindingName} is set, the view instance will be included in the bindings.
* <p>
* All the variables in the {@link #scriptBindings} will also be included in the bindings.
*
* @param bindings
* the bindings
* @param container
* the compontent that will be passed into the script
*
* @see #setContainerBindingName(String)
* @see #setViewBindingName(String)
* @see #setScriptBindings(Map)
*/
protected void populateBindings(Bindings bindings, JComponent container) {
if (StringUtils.hasText(containerBindingName)) {
bindings.put(containerBindingName, container);
}
if (StringUtils.hasText(viewBindingName)) {
bindings.put(viewBindingName, this);
}
if (this.scriptBindings != null) {
for (String key : scriptBindings.keySet()) {
bindings.put(key, scriptBindings.get((key)));
}
}
}
public void setScript(Resource script) {
this.script = script;
}
/**
* Sets the name of the engine to be created. This name will be used to create the engine.
*
* @param name
* the name
*
* @see ScriptEngineManager#getEngineByName(String)
*/
public void setEngineName(String name) {
engineName = name;
}
/**
* Set the bindings to be passed to the script.
*
* @param bindings
* the bindings
*/
public void setScriptBindings(Map<String, Object> bindings) {
// be nice and take a copy
this.scriptBindings = new HashMap<String, Object>(bindings);
}
/**
* Sets the view binding name.
*
* @param viewBindingName
* the name
* @see #populateBindings(Bindings, JComponent) for more details
*/
public void setViewBindingName(String viewBindingName) {
this.viewBindingName = viewBindingName;
}
/**
* Sets the container binding name.
*
* @param containerBindingName
* the name
* @see #populateBindings(Bindings, JComponent) for more details
*/
public void setContainerBindingName(String containerBindingName) {
this.containerBindingName = containerBindingName;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(script, "script must be set");
}
}