
org.scijava.script.process.ParameterScriptProcessor Maven / Gradle / Ivy
/*
* #%L
* SciJava Common shared library for SciJava software.
* %%
* Copyright (C) 2009 - 2017 Board of Regents of the University of
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
* Institute of Molecular Cell Biology and Genetics, University of
* Konstanz, and KNIME GmbH.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package org.scijava.script.process;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.ScriptException;
import org.scijava.ItemIO;
import org.scijava.ItemVisibility;
import org.scijava.command.Command;
import org.scijava.convert.ConvertService;
import org.scijava.log.LogService;
import org.scijava.module.DefaultMutableModuleItem;
import org.scijava.parse.ParseService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.script.ScriptInfo;
import org.scijava.script.ScriptModule;
import org.scijava.script.ScriptService;
/**
* A {@link ScriptProcessor} which parses the script's input and output
* parameters from the script header.
*
* SciJava's scripting framework supports specifying @{@link Parameter}-style
* inputs and outputs in a preamble. The format is a simplified version of the
* Java @{@link Parameter} annotation syntax. The following syntaxes are
* supported:
*
*
* - {@code #@
}
* - {@code #@
(=, ..., =) }
* - {@code #@
}
* - {@code #@
}
* - {@code #@
(=, ..., =)
* }
*
*
* Where:
*
*
* - {@code #@} - signals a special script processing instruction, so that the
* parameter line is ignored by the script engine itself.
* - {@code
} - one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}.
*
* - {@code
} - the name of the input or output variable.
* - {@code
} - the Java {@link Class} of the variable, or
* {@link Object} if none specified.
* - {@code
} - an attribute key.
* - {@code
} - an attribute value.
*
*
* See the @{@link Parameter} annotation for a list of valid attributes.
*
*
* Here are a few examples:
*
*
* - {@code #@Dataset dataset}
* - {@code #@double(type=OUTPUT) result}
* - {@code #@both ImageDisplay display}
* - {@code #@input(persist=false, visibility=INVISIBLE) boolean verbose}
* - {@code #@output thing}
*
*
* Parameters will be parsed and filled just like @{@link Parameter}-annotated
* fields in {@link Command}s.
*
*
* @author Curtis Rueden
*/
@Plugin(type = ScriptProcessor.class)
public class ParameterScriptProcessor implements ScriptProcessor {
@Parameter
private ScriptService scriptService;
@Parameter
private ConvertService convertService;
@Parameter
private ParseService parser;
@Parameter
private LogService log;
private ScriptInfo info;
private boolean header;
// -- ScriptProcessor methods --
@Override
public void begin(final ScriptInfo scriptInfo) {
info = scriptInfo;
info.setReturnValueAppended(true);
header = true;
}
@Override
public String process(final String line) {
// parse new-style parameters starting with @# anywhere in the script.
if (line.matches("^#@.*")) {
final int at = line.indexOf('@');
return process(line, line.substring(at + 1));
}
// parse old-style parameters in the initial script header
if (header) {
// NB: Check if line contains an '@' with no prior alphameric
// characters. This assumes that only non-alphanumeric characters can
// be used as comment line markers.
if (line.matches("^[^\\w]*@.*")) {
final int at = line.indexOf('@');
return process(line, line.substring(at + 1));
}
else if (line.matches(".*\\w.*")) header = false;
}
return line;
}
@Override
public void end() {
if (info.isReturnValueAppended()) {
// add an output for the value returned by the script itself
final HashMap attrs = new HashMap<>();
attrs.put("type", "OUTPUT");
addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false);
}
}
// -- Helper methods --
private String process(final String line, final String param) {
if (parseParam(param)) return "";
log.warn("Ignoring invalid parameter: " + param);
return line;
}
private boolean parseParam(final String param) {
final int lParen = param.indexOf("(");
final int rParen = param.lastIndexOf(")");
if (rParen < lParen) return false;
if (lParen < 0) return parseParam(param, parseAttrs("()"));
final String cutParam =
param.substring(0, lParen) + param.substring(rParen + 1);
final String attrs = param.substring(lParen + 1, rParen);
return parseParam(cutParam, parseAttrs(attrs));
}
private boolean parseParam(final String param,
final Map attrs)
{
final String[] tokens = param.trim().split("[ \t\n]+");
if (tokens.length < 1) return false;
final String typeName, varName;
final String maybeIOType = tokens[0].toUpperCase();
if (isIOType(maybeIOType)) {
if (tokens.length == 2) {
//
typeName = "Object";
varName = tokens[1];
}
else if (tokens.length == 3) {
//
typeName = tokens[1];
varName = tokens[2];
}
else return false;
attrs.put("type", maybeIOType);
}
else {
// assume syntax:
if (tokens.length < 2) return false;
typeName = tokens[0];
varName = tokens[1];
}
try {
final Class> type = scriptService.lookupClass(typeName);
addItem(varName, type, attrs, true);
}
catch (final ScriptException exc) {
log.warn("Invalid class: " + typeName, exc);
return false;
}
if (ScriptModule.RETURN_VALUE.equals(varName)) {
// NB: The return value variable is declared as an explicit parameter.
// So we should not append the return value as an extra output.
info.setReturnValueAppended(false);
}
return true;
}
/** Parses a comma-delimited list of {@code key=value} pairs into a map. */
private Map parseAttrs(final String attrs) {
return parser.parse(attrs, false).asMap();
}
private boolean isIOType(final String token) {
return convertService.convert(token.toUpperCase(), ItemIO.class) != null;
}
private void addItem(final String name, final Class type,
final Map attrs, final boolean explicit)
{
final DefaultMutableModuleItem item =
new DefaultMutableModuleItem<>(info, name, type);
for (final String key : attrs.keySet()) {
final Object value = attrs.get(key);
assignAttribute(item, key, value);
}
if (item.isInput()) info.registerInput(item);
if (item.isOutput()) {
info.registerOutput(item);
// NB: Only append the return value as an extra
// output when no explicit outputs are declared.
if (explicit) info.setReturnValueAppended(false);
}
}
private void assignAttribute(final DefaultMutableModuleItem item,
final String k, final Object v)
{
// CTR: There must be an easier way to do this.
// Just compile the thing using javac? Or parse via javascript, maybe?
if (is(k, "callback")) item.setCallback(as(v, String.class));
else if (is(k, "choices")) item.setChoices(asList(v, item.getType()));
else if (is(k, "columns")) item.setColumnCount(as(v, int.class));
else if (is(k, "description")) item.setDescription(as(v, String.class));
else if (is(k, "initializer")) item.setInitializer(as(v, String.class));
else if (is(k, "validater")) item.setValidater(as(v, String.class));
else if (is(k, "type")) item.setIOType(as(v, ItemIO.class));
else if (is(k, "label")) item.setLabel(as(v, String.class));
else if (is(k, "max")) item.setMaximumValue(as(v, item.getType()));
else if (is(k, "min")) item.setMinimumValue(as(v, item.getType()));
else if (is(k, "name")) item.setName(as(v, String.class));
else if (is(k, "persist")) item.setPersisted(as(v, boolean.class));
else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class));
else if (is(k, "required")) item.setRequired(as(v, boolean.class));
else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType()));
else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType()));
else if (is(k, "stepSize")) item.setStepSize(as(v, double.class));
else if (is(k, "style")) item.setWidgetStyle(as(v, String.class));
else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class));
else if (is(k, "value")) item.setDefaultValue(as(v, item.getType()));
else item.set(k, v.toString());
}
/** Super terse comparison helper method. */
private boolean is(final String key, final String desired) {
return desired.equalsIgnoreCase(key);
}
/** Super terse conversion helper method. */
private T as(final Object v, final Class type) {
final T converted = convertService.convert(v, type);
if (converted != null) return converted;
// NB: Attempt to convert via string.
// This is useful in cases where a weird type of object came back
// (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a
// nice string representation which ultimately is expressible as the type.
return convertService.convert(v.toString(), type);
}
private List asList(final Object v, final Class type) {
final ArrayList result = new ArrayList<>();
final List> list = as(v, List.class);
for (final Object item : list) {
result.add(as(item, type));
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy