blob: f45988e0f2bfbe4daec5a62e5c13f6e9b87a2200 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.apache.tools.ant.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.PropertyResource;
import org.apache.tools.ant.types.resources.StringResource;
/**
* This is a common abstract base case for script runners.
* These classes need to implement executeScript, evaluateScript
* and supportsLanguage.
* @since Ant 1.7.0
*/
public abstract class ScriptRunnerBase {
/** Whether to keep the engine between calls to execute/eval */
private boolean keepEngine = false;
/** Script language */
private String language;
/** Script content */
private String script = "";
private String encoding;
/** Enable script compilation. */
private boolean compiled;
/** Project this runner is used in */
private Project project;
/** Classloader to be used when running the script. */
private ClassLoader scriptLoader;
/** Beans to be provided to the script */
private final Map<String, Object> beans = new HashMap<>();
/**
* Add a list of named objects to the list to be exported to the script
*
* @param dictionary a map of objects to be placed into the script context
* indexed by String names.
*/
public void addBeans(Map<String, ?> dictionary) {
dictionary.forEach((k, v) -> {
try {
addBean(k, v);
} catch (BuildException ex) {
// The key is in the dictionary but cannot be retrieved
// This is usually due references that refer to tasks
// that have not been taskdefed in the current run.
// Ignore
}
});
}
/**
* Add a single object into the script context.
*
* @param key the name in the context this object is to stored under.
* @param bean the object to be stored in the script context.
*/
public void addBean(String key, Object bean) {
if (!key.isEmpty() && Character.isJavaIdentifierStart(key.charAt(0))
&& key.chars().skip(1).allMatch(Character::isJavaIdentifierPart)) {
beans.put(key, bean);
}
}
/**
* Get the beans used for the script.
* @return the map of beans.
*/
protected Map<String, Object> getBeans() {
return beans;
}
/**
* Do the work.
* @param execName the name that will be passed to BSF for this script
* execution.
*/
public abstract void executeScript(String execName);
/**
* Evaluate the script.
* @param execName the name that will be passed to the
* scripting engine for this script execution.
* @return the result of evaluating the script.
*/
public abstract Object evaluateScript(String execName);
/**
* Check if a script engine can be created for
* this language.
* @return true if a script engine can be created, false
* otherwise.
*/
public abstract boolean supportsLanguage();
/**
* Get the name of the manager prefix used for this
* scriptrunner.
* @return the prefix string.
*/
public abstract String getManagerName();
/**
* Defines the language (required).
* @param language the scripting language name for the script.
*/
public void setLanguage(String language) {
this.language = language;
}
/**
* Get the script language
* @return the script language
*/
public String getLanguage() {
return language;
}
/**
* Set the script classloader.
* @param classLoader the classloader to use.
*/
public void setScriptClassLoader(ClassLoader classLoader) {
this.scriptLoader = classLoader;
}
/**
* Get the classloader used to load the script engine.
* @return the classloader.
*/
protected ClassLoader getScriptClassLoader() {
return scriptLoader;
}
/**
* Whether to keep the script engine between calls.
* @param keepEngine if true, keep the engine.
*/
public void setKeepEngine(boolean keepEngine) {
this.keepEngine = keepEngine;
}
/**
* Get the keep engine attribute.
* @return the attribute.
*/
public boolean getKeepEngine() {
return keepEngine;
}
/**
* Whether to use script compilation if available.
* @since Ant 1.10.2
* @param compiled if true, compile the script if possible.
*/
public final void setCompiled(boolean compiled) {
this.compiled = compiled;
}
/**
* Get the compiled attribute.
* @since Ant 1.10.2
* @return the attribute.
*/
public final boolean getCompiled() {
return compiled;
}
/**
* Set encoding of the script from an external file; optional.
* @since Ant 1.10.2
* @param encoding encoding of the external file containing the script source.
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Load the script from an external file; optional.
* @param file the file containing the script source.
*/
public void setSrc(File file) {
String filename = file.getPath();
if (!file.exists()) {
throw new BuildException("file " + filename + " not found.");
}
try (InputStream in = Files.newInputStream(file.toPath())) {
final Charset charset = null == encoding ? Charset.defaultCharset()
: Charset.forName(encoding);
readSource(in, filename, charset);
} catch (IOException e) {
//this can only happen if the file got deleted a short moment ago
throw new BuildException("file " + filename + " not found.", e);
}
}
/**
* Read some source in from the given reader
* @param in the input stream to pass into a buffered reader.
* @param name the name to use in error messages
* @param charset the encoding for the reader, may be null.
*/
private void readSource(InputStream in, String name, Charset charset) {
try (Reader reader =
new BufferedReader(new InputStreamReader(in, charset))) {
script += FileUtils.safeReadFully(reader);
} catch (IOException ex) {
throw new BuildException("Failed to read " + name, ex);
}
}
/**
* Add a resource to the source list.
* @since Ant 1.7.1
* @param sourceResource the resource to load
* @throws BuildException if the resource cannot be read
*/
public void loadResource(Resource sourceResource) {
if (sourceResource instanceof StringResource) {
script += ((StringResource) sourceResource).getValue();
return;
}
if (sourceResource instanceof PropertyResource) {
script += ((PropertyResource) sourceResource).getValue();
return;
}
String name = sourceResource.toLongString();
try (InputStream in = sourceResource.getInputStream()) {
readSource(in, name, Charset.defaultCharset());
} catch (IOException e) {
throw new BuildException("Failed to open " + name, e);
} catch (UnsupportedOperationException e) {
throw new BuildException(
"Failed to open " + name + " - it is not readable", e);
}
}
/**
* Add all resources in a resource collection to the source list.
* @since Ant 1.7.1
* @param collection the resource to load
* @throws BuildException if a resource cannot be read
*/
public void loadResources(ResourceCollection collection) {
collection.forEach(this::loadResource);
}
/**
* Set the script text. Properties in the text are not expanded!
*
* @param text a component of the script text to be added.
*/
public void addText(String text) {
script += text;
}
/**
* Get the current script text content.
* @return the script text.
*/
public String getScript() {
return script;
}
/**
* Clear the current script text content.
*/
public void clearScript() {
this.script = "";
}
/**
* Set the project for this runner.
* @param project the project.
*/
public void setProject(Project project) {
this.project = project;
}
/**
* Get the project for this runner.
* @return the project.
*/
public Project getProject() {
return project;
}
/**
* Bind the runner to a project component.
* Properties, targets and references are all added as beans;
* project is bound to project, and self to the component.
* @param component to become <code>self</code>
*/
public void bindToComponent(ProjectComponent component) {
project = component.getProject();
addBeans(project.getProperties());
addBeans(project.getUserProperties());
addBeans(project.getCopyOfTargets());
addBeans(project.getCopyOfReferences());
addBean("project", project);
addBean("self", component);
}
/**
* Bind the runner to a project component.
* The project and self are the only beans set.
* @param component to become <code>self</code>
*/
public void bindToComponentMinimum(ProjectComponent component) {
project = component.getProject();
addBean("project", project);
addBean("self", component);
}
/**
* Check if the language attribute is set.
* @throws BuildException if it is not.
*/
protected void checkLanguage() {
if (language == null) {
throw new BuildException("script language must be specified");
}
}
/**
* Replace the current context classloader with the
* script context classloader.
* @return the current context classloader.
*/
protected ClassLoader replaceContextLoader() {
ClassLoader origContextClassLoader =
Thread.currentThread().getContextClassLoader();
if (getScriptClassLoader() == null) {
setScriptClassLoader(getClass().getClassLoader());
}
Thread.currentThread().setContextClassLoader(getScriptClassLoader());
return origContextClassLoader;
}
/**
* Restore the context loader with the original context classloader.
*
* script context loader.
* @param origLoader the original context classloader.
*/
protected void restoreContextLoader(ClassLoader origLoader) {
Thread.currentThread().setContextClassLoader(
origLoader);
}
}