Skip to content

Commit 50d4997

Browse files
committed
Externalize the script parameter parsing logic
It now lives in a new ParameterScriptProcessor. For the moment, script processing is triggered internally by the ScriptInfo's parseParameters method, which is a bit hacky.
1 parent bcb2454 commit 50d4997

File tree

3 files changed

+300
-230
lines changed

3 files changed

+300
-230
lines changed

src/main/java/org/scijava/script/DefaultScriptService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public class DefaultScriptService extends
9191
@Parameter
9292
private AppService appService;
9393

94+
@Parameter
95+
private ScriptProcessorService scriptProcessorService;
96+
9497
@Parameter
9598
private ParseService parser;
9699

src/main/java/org/scijava/script/ScriptInfo.java

Lines changed: 11 additions & 230 deletions
Original file line numberDiff line numberDiff line change
@@ -33,45 +33,31 @@
3333

3434
import java.io.BufferedReader;
3535
import java.io.File;
36-
import java.io.FileReader;
3736
import java.io.IOException;
3837
import java.io.InputStreamReader;
3938
import java.io.Reader;
4039
import java.io.StringReader;
4140
import java.net.MalformedURLException;
4241
import java.net.URL;
4342
import java.text.SimpleDateFormat;
44-
import java.util.ArrayList;
4543
import java.util.Date;
46-
import java.util.HashMap;
4744
import java.util.List;
48-
import java.util.Map;
49-
50-
import javax.script.ScriptException;
5145

5246
import org.scijava.Context;
5347
import org.scijava.Contextual;
54-
import org.scijava.ItemIO;
55-
import org.scijava.ItemVisibility;
5648
import org.scijava.NullContextException;
57-
import org.scijava.command.Command;
58-
import org.scijava.convert.ConvertService;
5949
import org.scijava.log.LogService;
6050
import org.scijava.module.AbstractModuleInfo;
61-
import org.scijava.module.DefaultMutableModuleItem;
6251
import org.scijava.module.ModuleException;
6352
import org.scijava.module.ModuleItem;
64-
import org.scijava.parse.ParseService;
6553
import org.scijava.plugin.Parameter;
54+
import org.scijava.script.process.ParameterScriptProcessor;
55+
import org.scijava.script.process.ScriptProcessorService;
6656
import org.scijava.util.DigestUtils;
6757
import org.scijava.util.FileUtils;
6858

6959
/**
7060
* Metadata about a script.
71-
* <p>
72-
* This class is responsible for parsing the script for parameters. See
73-
* {@link #parseParameters()} for details.
74-
* </p>
7561
*
7662
* @author Curtis Rueden
7763
* @author Johannes Schindelin
@@ -94,10 +80,7 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual {
9480
private ScriptService scriptService;
9581

9682
@Parameter
97-
private ParseService parser;
98-
99-
@Parameter
100-
private ConvertService convertService;
83+
private ScriptProcessorService scriptProcessorService;
10184

10285
/** True iff the return value should be appended as an output. */
10386
private boolean appendReturnValue;
@@ -265,86 +248,21 @@ public void setReturnValueAppended(final boolean appendReturnValue) {
265248
// -- AbstractModuleInfo methods --
266249

267250
/**
268-
* Parses the script's input and output parameters from the script header.
269-
* <p>
270-
* This method is called automatically the first time any parameter accessor
271-
* method is called ({@link #getInput}, {@link #getOutput}, {@link #inputs()},
272-
* {@link #outputs()}, etc.). Subsequent calls will reparse the parameters.
273-
* <p>
274-
* SciJava's scripting framework supports specifying @{@link Parameter}-style
275-
* inputs and outputs in a preamble. The format is a simplified version of the
276-
* Java @{@link Parameter} annotation syntax. The following syntaxes are
277-
* supported:
278-
* </p>
279-
* <ul>
280-
* <li>{@code // @<type> <varName>}</li>
281-
* <li>{@code // @<type>(<attr1>=<value1>, ..., <attrN>=<valueN>) <varName>}
282-
* </li>
283-
* <li>{@code // @<IOType> <type> <varName>}</li>
284-
* <li>{@code // @<IOType>(<attr1>=<value1>, ..., <attrN>=<valueN>) <type>
285-
* <varName>}</li>
286-
* </ul>
287-
* <p>
288-
* Where:
289-
* </p>
290-
* <ul>
291-
* <li>{@code //} = the comment style of the scripting language, so that the
292-
* parameter line is ignored by the script engine itself.</li>
293-
* <li>{@code <IOType>} = one of {@code INPUT}, {@code OUTPUT}, or
294-
* {@code BOTH}.</li>
295-
* <li>{@code <varName>} = the name of the input or output variable.</li>
296-
* <li>{@code <type>} = the Java {@link Class} of the variable.</li>
297-
* <li>{@code <attr*>} = an attribute key.</li>
298-
* <li>{@code <value*>} = an attribute value.</li>
299-
* </ul>
300-
* <p>
301-
* See the @{@link Parameter} annotation for a list of valid attributes.
302-
* </p>
303-
* <p>
304-
* Here are a few examples:
305-
* </p>
306-
* <ul>
307-
* <li>{@code // @Dataset dataset}</li>
308-
* <li>{@code // @double(type=OUTPUT) result}</li>
309-
* <li>{@code // @BOTH ImageDisplay display}</li>
310-
* <li>{@code // @INPUT(persist=false, visibility=INVISIBLE) boolean verbose}
311-
* </li>
312-
* </ul>
313-
* <p>
314-
* Parameters will be parsed and filled just like @{@link Parameter}-annotated
315-
* fields in {@link Command}s.
316-
* </p>
251+
* Performs script processing. In particular, parses the script parameters.
252+
*
253+
* @see ParameterScriptProcessor
254+
* @see ScriptProcessorService#process
317255
*/
318256
// NB: Widened visibility from AbstractModuleInfo.
319257
@Override
320258
public void parseParameters() {
321259
clearParameters();
322-
appendReturnValue = true;
323-
324-
try (final BufferedReader in = script == null ? //
325-
new BufferedReader(new FileReader(getPath())) : getReader()) //
326-
{
327-
while (true) {
328-
final String line = in.readLine();
329-
if (line == null) break;
330-
331-
// NB: Scan for lines containing an '@' with no prior alphameric
332-
// characters. This assumes that only non-alphanumeric characters can
333-
// be used as comment line markers.
334-
if (line.matches("^[^\\w]*@.*")) {
335-
final int at = line.indexOf('@');
336-
parseParam(line.substring(at + 1));
337-
}
338-
else if (line.matches(".*\\w.*")) break;
339-
}
340-
341-
if (appendReturnValue) addReturnValue();
260+
try {
261+
scriptProcessorService.process(this);
342262
}
343263
catch (final IOException exc) {
344-
log.error("Error reading script: " + path, exc);
345-
}
346-
catch (final ScriptException exc) {
347-
log.error("Invalid parameter syntax for script: " + path, exc);
264+
// TODO: Consider a better error handling approach.
265+
throw new RuntimeException(exc);
348266
}
349267
}
350268

@@ -452,143 +370,6 @@ private String path(final URL u, final String p) {
452370
return u == null ? null : u.getPath();
453371
}
454372

455-
private void parseParam(final String param) throws ScriptException {
456-
final int lParen = param.indexOf("(");
457-
final int rParen = param.lastIndexOf(")");
458-
if (rParen < lParen) {
459-
throw new ScriptException("Invalid parameter: " + param);
460-
}
461-
if (lParen < 0) parseParam(param, parseAttrs("()"));
462-
else {
463-
final String cutParam =
464-
param.substring(0, lParen) + param.substring(rParen + 1);
465-
final String attrs = param.substring(lParen + 1, rParen);
466-
parseParam(cutParam, parseAttrs(attrs));
467-
}
468-
}
469-
470-
private void parseParam(final String param,
471-
final Map<String, Object> attrs) throws ScriptException
472-
{
473-
final String[] tokens = param.trim().split("[ \t\n]+");
474-
checkValid(tokens.length >= 1, param);
475-
final String typeName, varName;
476-
if (isIOType(tokens[0])) {
477-
// assume syntax: <IOType> <type> <varName>
478-
checkValid(tokens.length >= 3, param);
479-
attrs.put("type", tokens[0]);
480-
typeName = tokens[1];
481-
varName = tokens[2];
482-
}
483-
else {
484-
// assume syntax: <type> <varName>
485-
checkValid(tokens.length >= 2, param);
486-
typeName = tokens[0];
487-
varName = tokens[1];
488-
}
489-
final Class<?> type = scriptService.lookupClass(typeName);
490-
addItem(varName, type, attrs, true);
491-
492-
if (ScriptModule.RETURN_VALUE.equals(varName)) {
493-
// NB: The return value variable is declared as an explicit OUTPUT.
494-
// So we should not append the return value as an extra output.
495-
appendReturnValue = false;
496-
}
497-
}
498-
499-
/** Parses a comma-delimited list of {@code key=value} pairs into a map. */
500-
private Map<String, Object> parseAttrs(final String attrs) {
501-
return parser.parse(attrs, false).asMap();
502-
}
503-
504-
private boolean isIOType(final String token) {
505-
return convertService.convert(token, ItemIO.class) != null;
506-
}
507-
508-
private void checkValid(final boolean valid, final String param)
509-
throws ScriptException
510-
{
511-
if (!valid) throw new ScriptException("Invalid parameter: " + param);
512-
}
513-
514-
/** Adds an output for the value returned by the script itself. */
515-
private void addReturnValue() {
516-
final HashMap<String, Object> attrs = new HashMap<>();
517-
attrs.put("type", "OUTPUT");
518-
addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false);
519-
}
520-
521-
private <T> void addItem(final String name, final Class<T> type,
522-
final Map<String, Object> attrs, final boolean explicit)
523-
{
524-
final DefaultMutableModuleItem<T> item =
525-
new DefaultMutableModuleItem<>(this, name, type);
526-
for (final String key : attrs.keySet()) {
527-
final Object value = attrs.get(key);
528-
assignAttribute(item, key, value);
529-
}
530-
if (item.isInput()) registerInput(item);
531-
if (item.isOutput()) {
532-
registerOutput(item);
533-
// NB: Only append the return value as an extra
534-
// output when no explicit outputs are declared.
535-
if (explicit) appendReturnValue = false;
536-
}
537-
}
538-
539-
private <T> void assignAttribute(final DefaultMutableModuleItem<T> item,
540-
final String k, final Object v)
541-
{
542-
// CTR: There must be an easier way to do this.
543-
// Just compile the thing using javac? Or parse via javascript, maybe?
544-
if (is(k, "callback")) item.setCallback(as(v, String.class));
545-
else if (is(k, "choices")) item.setChoices(asList(v, item.getType()));
546-
else if (is(k, "columns")) item.setColumnCount(as(v, int.class));
547-
else if (is(k, "description")) item.setDescription(as(v, String.class));
548-
else if (is(k, "initializer")) item.setInitializer(as(v, String.class));
549-
else if (is(k, "validater")) item.setValidater(as(v, String.class));
550-
else if (is(k, "type")) item.setIOType(as(v, ItemIO.class));
551-
else if (is(k, "label")) item.setLabel(as(v, String.class));
552-
else if (is(k, "max")) item.setMaximumValue(as(v, item.getType()));
553-
else if (is(k, "min")) item.setMinimumValue(as(v, item.getType()));
554-
else if (is(k, "name")) item.setName(as(v, String.class));
555-
else if (is(k, "persist")) item.setPersisted(as(v, boolean.class));
556-
else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class));
557-
else if (is(k, "required")) item.setRequired(as(v, boolean.class));
558-
else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType()));
559-
else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType()));
560-
else if (is(k, "stepSize")) item.setStepSize(as(v, double.class));
561-
else if (is(k, "style")) item.setWidgetStyle(as(v, String.class));
562-
else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class));
563-
else if (is(k, "value")) item.setDefaultValue(as(v, item.getType()));
564-
else item.set(k, v.toString());
565-
}
566-
567-
/** Super terse comparison helper method. */
568-
private boolean is(final String key, final String desired) {
569-
return desired.equalsIgnoreCase(key);
570-
}
571-
572-
/** Super terse conversion helper method. */
573-
private <T> T as(final Object v, final Class<T> type) {
574-
final T converted = convertService.convert(v, type);
575-
if (converted != null) return converted;
576-
// NB: Attempt to convert via string.
577-
// This is useful in cases where a weird type of object came back
578-
// (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a
579-
// nice string representation which ultimately is expressible as the type.
580-
return convertService.convert(v.toString(), type);
581-
}
582-
583-
private <T> List<T> asList(final Object v, final Class<T> type) {
584-
final ArrayList<T> result = new ArrayList<>();
585-
final List<?> list = as(v, List.class);
586-
for (final Object item : list) {
587-
result.add(as(item, type));
588-
}
589-
return result;
590-
}
591-
592373
/**
593374
* Read entire contents of a Reader and return as String.
594375
*

0 commit comments

Comments
 (0)