3333
3434import java .io .BufferedReader ;
3535import java .io .File ;
36- import java .io .FileReader ;
3736import java .io .IOException ;
3837import java .io .InputStreamReader ;
3938import java .io .Reader ;
4039import java .io .StringReader ;
4140import java .net .MalformedURLException ;
4241import java .net .URL ;
4342import java .text .SimpleDateFormat ;
44- import java .util .ArrayList ;
4543import java .util .Date ;
46- import java .util .HashMap ;
4744import java .util .List ;
48- import java .util .Map ;
49-
50- import javax .script .ScriptException ;
5145
5246import org .scijava .Context ;
5347import org .scijava .Contextual ;
54- import org .scijava .ItemIO ;
55- import org .scijava .ItemVisibility ;
5648import org .scijava .NullContextException ;
57- import org .scijava .command .Command ;
58- import org .scijava .convert .ConvertService ;
5949import org .scijava .log .LogService ;
6050import org .scijava .module .AbstractModuleInfo ;
61- import org .scijava .module .DefaultMutableModuleItem ;
6251import org .scijava .module .ModuleException ;
6352import org .scijava .module .ModuleItem ;
64- import org .scijava .parse .ParseService ;
6553import org .scijava .plugin .Parameter ;
54+ import org .scijava .script .process .ParameterScriptProcessor ;
55+ import org .scijava .script .process .ScriptProcessorService ;
6656import org .scijava .util .DigestUtils ;
6757import 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