diff --git a/src/main/java/org/scijava/grape/DefaultGrapeService.java b/src/main/java/org/scijava/grape/DefaultGrapeService.java
new file mode 100644
index 000000000..a1fcdb775
--- /dev/null
+++ b/src/main/java/org/scijava/grape/DefaultGrapeService.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2017 SciJava.
+ *
+ * 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.scijava.grape;
+
+import groovy.grape.GrapeEngine;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.net.URI;
+import java.nio.file.Paths;
+import org.scijava.Context;
+import org.scijava.plugin.Parameter;
+import org.scijava.plugin.Plugin;
+import org.scijava.service.AbstractService;
+import org.scijava.service.Service;
+
+/**
+ * Facade to GrapeEngine. Kindly stolen from
+ * https://github.com/apache/groovy/blob/master/src/main/groovy/grape/Grape.java.
+ */
+@Plugin(type = Service.class)
+public class DefaultGrapeService extends AbstractService implements GrapeService {
+
+ public static final String AUTO_DOWNLOAD_SETTING = "autoDownload";
+ public static final String DISABLE_CHECKSUMS_SETTING = "disableChecksums";
+ public static final String SYSTEM_PROPERTIES_SETTING = "systemProperties";
+
+ @Parameter
+ private Context context;
+
+ private boolean enableGrapes = Boolean.valueOf(System.getProperty("org.scijava.grape.enable", "true"));
+ private boolean enableAutoDownload = Boolean.valueOf(System.getProperty("org.scijava.grape.autoDownload", "true"));
+ private boolean disableChecksums = Boolean.valueOf(System.getProperty("org.scijava.grape.disableChecksums", "false"));
+
+ private GrapeEngine grapeEngine = null;
+
+ /**
+ * This is a static access kill-switch. All of the static shortcut methods in this class will
+ * not work if this property is set to false. By default it is set to true.
+ *
+ * @return
+ */
+ @Override
+ public boolean getEnableGrapes() {
+ return enableGrapes;
+ }
+
+ /**
+ * This is a static access kill-switch. All of the static shortcut methods in this class will
+ * not work if this property is set to false. By default it is set to true.
+ *
+ * @param enableGrapes
+ */
+ @Override
+ public void setEnableGrapes(boolean enableGrapes) {
+ this.enableGrapes = enableGrapes;
+ }
+
+ /**
+ * This is a static access auto download enabler. It will set the 'autoDownload' value to the
+ * passed in arguments map if not already set. If 'autoDownload' is set the value will not be
+ * adjusted.
+ *
+ * This applies to the grab and resolve calls.
+ *
+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure
+ * in the grape call if the library has not yet been downloaded
+ *
+ * If it is set to true, then any jars not already downloaded will automatically be downloaded.
+ * Also, any versions expressed as a range will be checked for new versions and downloaded (with
+ * dependencies) if found.
+ *
+ * By default it is set to true.
+ */
+ @Override
+ public boolean getEnableAutoDownload() {
+ return enableAutoDownload;
+ }
+
+ /**
+ * This is a static access auto download enabler. It will set the 'autoDownload' value to the
+ * passed in arguments map if not already set. If 'autoDownload' is set the value will not be
+ * adjusted.
+ *
+ * This applies to the grab and resolve calls.
+ *
+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure
+ * in the grape call if the library has not yet been downloaded.
+ *
+ * If it is set to true, then any jars not already downloaded will automatically be downloaded.
+ * Also, any versions expressed as a range will be checked for new versions and downloaded (with
+ * dependencies) if found. By default it is set to true.
+ *
+ * @param enableAutoDownload
+ */
+ @Override
+ public void setEnableAutoDownload(boolean enableAutoDownload) {
+ this.enableAutoDownload = enableAutoDownload;
+ }
+
+ /**
+ * Global flag to ignore checksums. By default it is set to false.
+ */
+ @Override
+ public boolean getDisableChecksums() {
+ return disableChecksums;
+ }
+
+ /**
+ * Set global flag to ignore checksums. By default it is set to false.
+ *
+ * @param disableChecksums
+ */
+ @Override
+ public void setDisableChecksums(boolean disableChecksums) {
+ this.disableChecksums = disableChecksums;
+ }
+
+ @Override
+ public GrapeEngine getGrapeEngine() {
+ if (this.grapeEngine == null) {
+
+ // Set some settings for Ivy
+ System.setProperty("groovy.grape.report.downloads", "true");
+ System.setProperty("grape.root", Paths.get(System.getProperty("user.home"), ".scijava").toString());
+
+ // Initialize the GrapeEngine
+ this.grapeEngine = new GrapeScijava();
+ }
+ return grapeEngine;
+ }
+
+ @Override
+ public void grab(String endorsed) {
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ instance.grab(endorsed);
+ }
+ }
+ }
+
+ @Override
+ public void grab(Map dependency) {
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ if (!dependency.containsKey(AUTO_DOWNLOAD_SETTING)) {
+ dependency.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+ }
+ if (!dependency.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+ dependency.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+ }
+
+ if (!dependency.keySet().contains("classLoader")) {
+ dependency.put("classLoader", this.context().getClass().getClassLoader());
+ }
+
+ instance.grab(dependency);
+ }
+ }
+ }
+
+ @Override
+ public void grab(Map args, Map... dependencies) {
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) {
+ args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+ }
+ if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+ args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+ }
+
+ if (!args.keySet().contains("classLoader")) {
+ args.put("classLoader", this.context().getClass().getClassLoader());
+ }
+
+ instance.grab(args, dependencies);
+ }
+ }
+ }
+
+ @Override
+ public Map>> enumerateGrapes() {
+ Map>> grapes = null;
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ grapes = instance.enumerateGrapes();
+ }
+ }
+ if (grapes == null) {
+ return Collections.emptyMap();
+ } else {
+ return grapes;
+ }
+ }
+
+ @Override
+ public URI[] resolve(Map args, Map... dependencies) {
+ return resolve(args, null, dependencies);
+ }
+
+ @Override
+ public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
+ URI[] uris = null;
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) {
+ args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+ }
+ if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+ args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+ }
+ uris = instance.resolve(args, depsInfo, dependencies);
+ }
+ }
+ if (uris == null) {
+ return new URI[0];
+ } else {
+ return uris;
+ }
+ }
+
+ @Override
+ public Map[] listDependencies(ClassLoader cl) {
+ Map[] maps = null;
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ maps = instance.listDependencies(cl);
+ }
+ }
+ if (maps == null) {
+ return new Map[0];
+ } else {
+ return maps;
+ }
+
+ }
+
+ @Override
+ public void addResolver(Map args) {
+ if (enableGrapes) {
+ GrapeEngine instance = getGrapeEngine();
+ if (instance != null) {
+ instance.addResolver(args);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/scijava/grape/GrapeScijava.java b/src/main/java/org/scijava/grape/GrapeScijava.java
new file mode 100644
index 000000000..1ea9d00e7
--- /dev/null
+++ b/src/main/java/org/scijava/grape/GrapeScijava.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017 SciJava.
+ *
+ * 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.scijava.grape;
+
+import groovy.grape.GrapeIvy;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.codehaus.groovy.reflection.ReflectionUtils;
+
+/**
+ * I had to extend GrapeIvy to use any CLassLoader (not only GroovyClassLoader).
+ *
+ * @author Hadrien Mary
+ */
+public class GrapeScijava extends GrapeIvy {
+
+ Map> exclusiveGrabArgs = new HashMap>() {
+ {
+ put("group", Arrays.asList("groupId", "organisation", "organization", "org"));
+ put("groupId", Arrays.asList("group", "organisation", "organization", "org"));
+ put("organisation", Arrays.asList("group", "groupId", "organization", "org"));
+ put("organization", Arrays.asList("group", "groupId", "organisation", "org"));
+ put("org", Arrays.asList("group", "groupId", "organisation", "organization"));
+ put("module", Arrays.asList("artifactId", "artifact"));
+ put("artifactId", Arrays.asList("module", "artifact"));
+ put("artifact", Arrays.asList("module", "artifactId"));
+ put("version", Arrays.asList("revision", "rev"));
+ put("revision", Arrays.asList("version", "rev"));
+ put("rev", Arrays.asList("version", "revision"));
+ put("conf", Arrays.asList("scope", "configuration"));
+ put("scope", Arrays.asList("conf", "configuration"));
+ put("configuration", Arrays.asList("conf", "scope"));
+
+ }
+ };
+
+ @Override
+ public ClassLoader chooseClassLoader(Map args) {
+ ClassLoader loader = (ClassLoader) args.get("classLoader");
+
+ if (this.isValidTargetClassLoader(loader)) {
+ if (args.get("refObject") == null) {
+ if (!args.keySet().contains("calleeDepth")) {
+ loader = ReflectionUtils.getCallingClass((int) args.get("calleeDepth")).getClassLoader();
+ } else {
+ loader = ReflectionUtils.getCallingClass(1).getClassLoader();
+ }
+ }
+
+ while (loader != null && !this.isValidTargetClassLoader(loader)) {
+ loader = loader.getParent();
+ }
+ //if (!isValidTargetClassLoader(loader)) {
+ // loader = Thread.currentThread().contextClassLoader
+ //}
+ //if (!isValidTargetClassLoader(loader)) {
+ // loader = GrapeIvy.class.classLoader
+ //}
+ if (!isValidTargetClassLoader(loader)) {
+ throw new RuntimeException("No suitable ClassLoader found for grab");
+ }
+ }
+ return loader;
+ }
+
+ private boolean isValidTargetClassLoader(ClassLoader loader) {
+ if (loader != null) {
+ return loader.getClass() == ClassLoader.class;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isValidTargetClassLoaderClass(Class loaderClass) {
+ return isValidTargetClassLoader(loaderClass.getClassLoader());
+ }
+
+ @Override
+ public File getLocalGrapeConfig() {
+
+ InputStream configStream = GrapeScijava.class.getResourceAsStream("/org/scijava/grape/scijavaGrapeConfig.xml");
+
+ // Copy the config file to a temporary file since
+ // the Groovy API only accept File object.
+ File configTempFile = new File("");
+ try {
+ configTempFile = File.createTempFile("scijavaGrapeConfig", ".xml");
+ try {
+ Files.copy(configStream, configTempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException ex) {
+ Logger.getLogger(GrapeScijava.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ } catch (IOException ex) {
+ Logger.getLogger(GrapeScijava.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ return configTempFile;
+ }
+}
diff --git a/src/main/java/org/scijava/grape/GrapeService.java b/src/main/java/org/scijava/grape/GrapeService.java
new file mode 100644
index 000000000..6195984da
--- /dev/null
+++ b/src/main/java/org/scijava/grape/GrapeService.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 SciJava.
+ *
+ * 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.scijava.grape;
+
+import groovy.grape.GrapeEngine;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import org.scijava.service.SciJavaService;
+
+/**
+ *
+ * @author Hadrien Mary
+ */
+public interface GrapeService extends SciJavaService {
+
+ void addResolver(Map args);
+
+ Map>> enumerateGrapes();
+
+ /**
+ * Global flag to ignore checksums. By default it is set to false.
+ * @return
+ */
+ boolean getDisableChecksums();
+
+ /**
+ * This is a static access auto download enabler. It will set the 'autoDownload' value to the
+ * passed in arguments map if not already set. If 'autoDownload' is set the value will not be
+ * adjusted.
+ *
+ * This applies to the grab and resolve calls.
+ *
+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure
+ * in the grape call if the library has not yet been downloaded
+ *
+ * If it is set to true, then any jars not already downloaded will automatically be downloaded.
+ * Also, any versions expressed as a range will be checked for new versions and downloaded (with
+ * dependencies) if found.
+ *
+ * By default it is set to true.
+ * @return
+ */
+ boolean getEnableAutoDownload();
+
+ /**
+ * This is a static access kill-switch. All of the static shortcut methods in this class will
+ * not work if this property is set to false. By default it is set to true.
+ */
+ boolean getEnableGrapes();
+
+ GrapeEngine getGrapeEngine();
+
+ void grab(String endorsed);
+
+ void grab(Map dependency);
+
+ void grab(Map args, Map... dependencies);
+
+ Map[] listDependencies(ClassLoader cl);
+
+ URI[] resolve(Map args, Map... dependencies);
+
+ URI[] resolve(Map args, List depsInfo, Map... dependencies);
+
+ /**
+ * Set global flag to ignore checksums. By default it is set to false.
+ */
+ void setDisableChecksums(boolean disableChecksums);
+
+ /**
+ * This is a static access auto download enabler. It will set the 'autoDownload' value to the
+ * passed in arguments map if not already set. If 'autoDownload' is set the value will not be
+ * adjusted.
+ *
+ * This applies to the grab and resolve calls.
+ *
+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure
+ * in the grape call if the library has not yet been downloaded.
+ *
+ * If it is set to true, then any jars not already downloaded will automatically be downloaded.
+ * Also, any versions expressed as a range will be checked for new versions and downloaded (with
+ * dependencies) if found. By default it is set to true.
+ */
+ void setEnableAutoDownload(boolean enableAutoDownload);
+
+ /**
+ * This is a static access kill-switch. All of the static shortcut methods in this class will
+ * not work if this property is set to false. By default it is set to true.
+ *
+ * @param enableGrapes
+ */
+ void setEnableGrapes(boolean enableGrapes);
+
+}
diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java
index b93751a31..1bad998ee 100644
--- a/src/main/java/org/scijava/script/ScriptInfo.java
+++ b/src/main/java/org/scijava/script/ScriptInfo.java
@@ -56,6 +56,7 @@
import org.scijava.NullContextException;
import org.scijava.command.Command;
import org.scijava.convert.ConvertService;
+import org.scijava.grape.GrapeService;
import org.scijava.log.LogService;
import org.scijava.module.AbstractModuleInfo;
import org.scijava.module.DefaultMutableModuleItem;
@@ -97,7 +98,7 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual {
@Parameter
private ConvertService convertService;
-
+
/** True iff the return value should be appended as an output. */
private boolean appendReturnValue;
@@ -421,7 +422,24 @@ private void parseParam(final String param,
attrs.put("type", tokens[0]);
typeName = tokens[1];
varName = tokens[2];
- }
+ } else if (tokens.length == 1) {
+ // Check for Grab or GrabResolver
+ GrapeService grape = this.context().getService(GrapeService.class);
+ typeName = tokens[0];
+ varName = null;
+
+ switch (typeName) {
+ case "Grab":
+ grape.grab(attrs);
+ return;
+ case "GrabResolver":
+ grape.resolve(attrs);
+ return;
+ default:
+ throw new ScriptException("Invalid parameter: " + param);
+ }
+ // Quit because we don't want to use addItem()
+ }
else {
// assume syntax:
checkValid(tokens.length >= 2, param);
diff --git a/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml b/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml
new file mode 100644
index 000000000..8d1823345
--- /dev/null
+++ b/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java
index 8de76276e..c98f2e122 100644
--- a/src/test/java/org/scijava/ContextCreationTest.java
+++ b/src/test/java/org/scijava/ContextCreationTest.java
@@ -94,6 +94,7 @@ public void testFull() {
org.scijava.convert.DefaultConvertService.class,
org.scijava.display.DefaultDisplayService.class,
org.scijava.event.DefaultEventHistory.class,
+ org.scijava.grape.DefaultGrapeService.class,
org.scijava.input.DefaultInputService.class,
org.scijava.io.DefaultDataHandleService.class,
org.scijava.io.DefaultIOService.class,