lines = new ArrayList<>(1024);
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ // processBuilder.inheritIO();
+ process = processBuilder.start();
+ // System.out.println("Running git process.");
+ BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()));
+
+ for (String line = bufferedReader.readLine(); line != null; line = bufferedReader
+ .readLine()) {
+ // System.out.println("lineRead " + line);
+ lines.add(line);
+ }
+ process.waitFor(1, TimeUnit.SECONDS);
+
+ } catch (IOException ioe) {
+ System.out.println("Writing error to latex file.");
+ try {
+ writeErrorToLatexFile(ioe, outputFile);
+ } catch (IOException ignored) {
+ }
+ throw ioe;
+ } finally {
+ if (process != null) {
+ try {
+ process.getErrorStream().close();
+ } catch (Exception ignored) {
+ }
+ try {
+ process.getOutputStream().close();
+ } catch (Exception ignored) {
+ }
+ try {
+ process.getInputStream().close();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ if (process.exitValue() != 0) {
+ throw new IOException(
+ "Process exited with non-zero exit code (" + process.exitValue() + ").");
+ }
+ return lines;
+ }
+
+ /**
+ * Returns a file name without its extension so blah.txt would become blah (no dot).
+ */
+ public static String stripFileExtension(String fname) {
+ int dotIndex = fname.lastIndexOf('.');
+ if (dotIndex == -1 || dotIndex == 0) {
+ return fname;
+ } else {
+ return fname.substring(0, dotIndex);
+ }
+ }
+
+ /**
+ * Returns the file extension
+ */
+ public static String fileExtension(String fname) {
+ int dotIndex = fname.lastIndexOf('.');
+ if (dotIndex == -1 || dotIndex == 0 || dotIndex == (fname.length() - 1)) {
+ return "";
+ } else {
+ return fname.substring(dotIndex + 1, fname.length());
+ }
+ }
+
+ public static String changeFileExtension(String fname, String newExtension) {
+ if (newExtension.charAt(0) != '.') {
+ return newExtension = "." + newExtension;
+ }
+ String prefix = stripFileExtension(fname);
+ return prefix + newExtension;
+ }
+
+ /**
+ * Given the specified file name this appends the creation date before the file name extension.
+ */
+ public static String appendCreationDate(String fname) {
+ String extension = fileExtension(fname);
+ if (extension.length() != 0) {
+ extension = "." + extension;
+ }
+ String prefix = stripFileExtension(fname);
+ // TODO: Is this the correct format?
+ SimpleDateFormat dateFormatter = new SimpleDateFormat("dd-MMM-yyyy");
+ return prefix + "-" + dateFormatter.format(new Date()) + extension;
+ }
+}
+
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/Mcc.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/Mcc.java
new file mode 100644
index 0000000..72c3bb2
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/Mcc.java
@@ -0,0 +1,221 @@
+package gov.nasa.ziggy.buildutil;
+
+import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.os.OperatingSystem;
+
+/**
+ * Compiles the matlab executables (i.e. it runs mcc). This class can not be made final.
+ *
+ * This class puts the variable MCC_DIR into the environment and sets it to the directory of the project that has
+ * invoked this task.
+ *
+ * @author Bill Wohler
+ * @author Sean McCauliff
+ * @author Forrest Girouard
+ */
+public class Mcc extends TessExecTask {
+
+ private static final Logger log = LoggerFactory.getLogger(Mcc.class);
+
+ private FileCollection controllerFiles;
+ private FileCollection additionalFiles; // added with mcc -a option
+ private File outputExecutable;
+ private boolean singleThreaded = true;
+
+ public Mcc() {
+ setEnabled(isMccEnabled());
+ }
+
+ @InputFiles
+ public FileCollection getControllerFiles() {
+ return controllerFiles;
+ }
+
+ public void setControllerFiles(FileCollection controllerFiles) {
+ this.controllerFiles = controllerFiles;
+ }
+
+ public FileCollection getAdditionalFiles() {
+ return additionalFiles;
+ }
+
+ public void setAdditionalFiles(FileCollection additionalFiles) {
+ this.additionalFiles = additionalFiles;
+ }
+
+ @OutputFile
+ public File getOutputExecutable() {
+ String path = outputExecutable.getPath();
+ if (OperatingSystem.MAC_OS == OperatingSystem.current()) {
+ path += ".app";
+ return new File(path, "Contents/MacOS/" + outputExecutable.getName());
+ }
+
+ return outputExecutable;
+ }
+
+ @OutputDirectory
+ public File getOutputApplication() {
+ String path = outputExecutable.getPath();
+ if (OperatingSystem.MAC_OS == OperatingSystem.current()) {
+ path += ".app";
+ return new File(path);
+ }
+
+ return outputExecutable.getParentFile();
+ }
+
+ public void setOutputExecutable(File outputExecutable) {
+ this.outputExecutable = outputExecutable;
+ }
+
+ @Optional
+ public boolean isSingleThreaded() {
+ return singleThreaded;
+ }
+
+ public void setSingleThreaded(boolean newValue) {
+ singleThreaded = newValue;
+ }
+
+ @TaskAction
+ public void action() {
+ log.info(String.format("%s.action()\n", this.getClass().getSimpleName()));
+ File matlabHome = matlabHome();
+
+ File buildBinDir = new File(getProject().getBuildDir(), "bin");
+ List command = new ArrayList<>();
+
+ command.addAll(Arrays.asList("mcc", "-v", "-m", "-N", "-d", buildBinDir.toString(), "-R",
+ "-nodisplay", "-R", "-nodesktop"));
+
+ if (isSingleThreaded()) {
+ command.add("-R");
+ command.add("-singleCompThread");
+ }
+
+ for (String s : new String[] { "stats", "signal" }) {
+ command.add("-p");
+ command.add(matlabHome + "/toolbox/" + s);
+ }
+
+ command.add("-o");
+ command.add(outputExecutable.getName());
+
+ String path = outputExecutable.getPath();
+ File executable = new File(path);
+ if (OperatingSystem.MAC_OS == OperatingSystem.current()) {
+ path += ".app";
+ executable = new File(path);
+ String message = String.format(
+ "The outputExecutable, \"%s\", already exists and cannot be deleted\n", executable);
+ if (executable.exists()) {
+ log.info(String.format("%s: already exists, delete it\n", executable));
+ if (executable.isDirectory()) {
+ try {
+ FileUtils.deleteDirectory(executable);
+ } catch (IOException e) {
+ log.error(message);
+ throw new GradleException(message, e);
+ }
+ } else if (!executable.delete()) {
+ log.error(message);
+ throw new GradleException(message);
+ }
+ }
+ if (executable.exists()) {
+ log.error(message);
+ throw new GradleException(message);
+ }
+ }
+
+ if (controllerFiles != null) {
+ for (File f : controllerFiles) {
+ command.add(f.toString());
+ }
+ }
+
+ if (additionalFiles != null) {
+ for (File f : additionalFiles) {
+ command.add("-a");
+ command.add(f.toString());
+ }
+ }
+
+ String cmd = command.stream().collect(Collectors.joining(" "));
+ List fullCommand = new ArrayList<>();
+ fullCommand.add("/bin/bash");
+ fullCommand.add("-c");
+ fullCommand.add(cmd);
+ cmd = fullCommand.stream().collect(Collectors.joining(" "));
+ log.info(cmd);
+
+ ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
+ try {
+ processBuilder.environment()
+ .put("MCC_DIR", getProject().getProjectDir().getCanonicalPath());
+ } catch (IOException e) {
+ log.error(String.format("Could not set MCC_DIR: %s", e.getMessage()), e);
+ }
+ execProcess(processBuilder);
+
+ Set neededPermissions = new HashSet<>(Arrays.asList(
+ new PosixFilePermission[] { OWNER_EXECUTE, GROUP_EXECUTE, OWNER_READ, GROUP_READ }));
+
+ if (!executable.exists()) {
+ String message = "The outputExecutable,\"" + executable + "\" does not exist.";
+ log.error(message);
+ throw new GradleException(message);
+ } else {
+ Set currentPosixFilePermissions = null;
+ try {
+ currentPosixFilePermissions = Files.getPosixFilePermissions(executable.toPath());
+ log.info(currentPosixFilePermissions.stream()
+ .map(p -> p.name())
+ .collect(Collectors.joining(" ", "Current file permissions are: ", ".")));
+ if (!neededPermissions.containsAll(currentPosixFilePermissions)) {
+ currentPosixFilePermissions.addAll(neededPermissions);
+ log.info(currentPosixFilePermissions.stream()
+ .map(p -> p.name())
+ .collect(Collectors.joining(" ", "Setting file permissions to: ", ",")));
+ Files.setPosixFilePermissions(executable.toPath(), currentPosixFilePermissions);
+ }
+ } catch (IOException ioe) {
+ String message = "Failed to either get or set permissions on outputExecutable \""
+ + outputExecutable;
+ log.error(message);
+ throw new GradleException(message);
+ }
+ }
+ File readme = new File(outputExecutable.getParentFile(), "readme.txt");
+ if (readme.exists()) {
+ readme.renameTo(new File(outputExecutable.getParentFile(),
+ outputExecutable.getName() + "-readme.txt"));
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/TessExecTask.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/TessExecTask.java
new file mode 100644
index 0000000..4d2ef1a
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/TessExecTask.java
@@ -0,0 +1,75 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.stream.Collectors;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+
+/***
+ * Gradle has an ExecTask, but you can't subclass it to make your own tasks.
+ *
+ * @author Sean McCauliff
+ */
+abstract class TessExecTask extends DefaultTask {
+
+ protected static boolean isMccEnabled() {
+ String mccEnabled = System.getenv("MCC_ENABLED");
+ if (mccEnabled == null || !Boolean.valueOf(mccEnabled)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected static File matlabHome() {
+ String matlabHome = System.getenv("MATLAB_HOME");
+ if (matlabHome == null) {
+ throw new GradleException("MATLAB_HOME is not set");
+ }
+ if (matlabHome.contains("2010")) {
+ throw new GradleException(
+ "MATLAB_HOME=" + matlabHome + ". This is not the MATLAB I'm looking for.");
+ }
+ File home = new File(matlabHome);
+ if (!home.exists()) {
+ throw new GradleException("MATLAB_HOME=\"" + home + "\" does not exist.");
+ }
+ return home;
+ }
+
+ /**
+ * @param command The first element in this list is the name of the executable. the others are
+ * arguments. No escaping is required.
+ */
+ protected static void execProcess(ProcessBuilder processBuilder) {
+
+ processBuilder.redirectErrorStream(true);
+ String commandString = processBuilder.command().stream().collect(Collectors.joining(" "));
+ try {
+ Process process = processBuilder.start();
+ process.waitFor();
+ if (process.exitValue() != 0) {
+ try (InputStreamReader inRead = new InputStreamReader(process.getInputStream());
+ BufferedReader breader = new BufferedReader(inRead)) {
+ StringBuilder bldr = new StringBuilder();
+ bldr.append(commandString).append('\n');
+ for (String line = breader.readLine(); line != null; line = breader
+ .readLine()) {
+ bldr.append(line).append("\n");
+ }
+ throw new GradleException(bldr.toString());
+ } catch (IOException ioe) {
+ throw new GradleException("Command \"" + commandString
+ + "\" Failed, no other information avaialble.");
+ }
+ }
+ } catch (IOException e) {
+ throw new GradleException("While trying to exec \"" + commandString + "\".", e);
+ } catch (InterruptedException e) {
+ throw new GradleException(commandString, e);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCpp.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCpp.java
new file mode 100644
index 0000000..f870025
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCpp.java
@@ -0,0 +1,191 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.File;
+import java.util.List;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import gov.nasa.ziggy.buildutil.ZiggyCppPojo.BuildType;
+
+/**
+ * Performs C++ builds for Gradle. Ziggy and its pipelines use this instead of the
+ * standard Gradle C++ task classes because it provides only the options we actually
+ * need without many that we can't use, and also because it allows us to use the CXX
+ * environment variable to define the location of the C++ compiler. This allows us to
+ * use the same compiler as some of the third party libraries used by Ziggy (they also
+ * use CXX to select their compiler).
+ *
+ * Because Gradle classes that extend DefaultTask are effectively impossible to unit test,
+ * all of the actual data and work are managed by the ZiggyCppPojo class (which does have
+ * unit tests), while this class simply provides access to the ZiggyCppPojo class for Gradle.
+ *
+ * @author PT
+ *
+ */
+public class ZiggyCpp extends DefaultTask {
+
+ /**
+ * Data and methods used to perform the C++ compile and link.
+ */
+ private ZiggyCppPojo ziggyCppPojo = new ZiggyCppPojo();
+
+ /**
+ * Default constructor. Provides the ZiggyCppPojo object with directories for the project and
+ * default options, if set
+ */
+ public ZiggyCpp() {
+ Project project = getProject();
+ ziggyCppPojo.setBuildDir(project.getBuildDir());
+ ziggyCppPojo.setRootDir(pipelineRootDir(project));
+ if (project.hasProperty(ZiggyCppPojo.DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY)) {
+ ziggyCppPojo.setCompileOptions(ZiggyCppPojo.gradlePropertyToList(
+ project.property(ZiggyCppPojo.DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppPojo.DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY)) {
+ ziggyCppPojo.setLinkOptions(ZiggyCppPojo.gradlePropertyToList(
+ project.property(ZiggyCppPojo.DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppPojo.DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY)) {
+ ziggyCppPojo.setReleaseOptimizations(ZiggyCppPojo.gradlePropertyToList(
+ project.findProperty(ZiggyCppPojo.DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppPojo.DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY)) {
+ ziggyCppPojo.setDebugOptimizations(ZiggyCppPojo.gradlePropertyToList(
+ project.findProperty(ZiggyCppPojo.DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY)));
+ }
+ }
+
+ /**
+ * Returns the root directory of the pipeline
+ *
+ * @param project Current project
+ * @return rootDir if the pipelineRootDir project property is not set; if that property is set,
+ * the contents of the pipelineRootDir property are returned as a File
+ */
+ public static File pipelineRootDir(Project project) {
+ File pipelineRootDir = null;
+ if (project.hasProperty(ZiggyCppPojo.PIPELINE_ROOT_DIR_PROP_NAME)) {
+ pipelineRootDir = new File(
+ project.property(ZiggyCppPojo.PIPELINE_ROOT_DIR_PROP_NAME).toString());
+ } else {
+ pipelineRootDir = project.getRootDir();
+ }
+ return pipelineRootDir;
+ }
+
+ /** Provides access to the ZiggyCppPojo action() method for Gradle. */
+ @TaskAction
+ public void action() {
+ ziggyCppPojo.action();
+ }
+
+ /**
+ * Provides access to the ZiggyCppPojo list of C++ files for Gradle, and specifies for gradle
+ * that those files are the inputs for this task.
+ *
+ * @return List of C++ files found in the C++ source file directory.
+ */
+ @InputFiles
+ public List getCppFiles() {
+ return ziggyCppPojo.getCppFiles();
+ }
+
+ /**
+ * Provides Gradle with access to the ZiggyCppPojo File that will be the final product of the
+ * build. Also specifies that this file is the output for this task.
+ *
+ * @return File containing the target product for a task.
+ */
+ @OutputFile
+ public File getBuiltFile() {
+ return ziggyCppPojo.getBuiltFile();
+ }
+
+ // Below are setters and getters for the ZiggyCppPojo members that must be mutated
+ // by ZiggyCpp tasks in gradle. In principle only setters are needed, but in the
+ // interest of sanity getters are also provided. Note that not all ZiggyCppPojo
+ // members need to be set by ZiggyCpp, there are a number that are used internally
+ // and are not set as part of a task.
+ //
+ // Note that the setters take Object and List rather than String and List.
+ // This is because Gradle allows its text string objects to be either Java String class or
+ // Groovy GString class. Consequently, we pass everything to ZiggyCppPojo as Objects, and
+ // ZiggyCppPojo uses the toString() methods to convert everything to Java Strings.
+
+ // Path to the C++ source files
+ public void setCppFilePaths(List cppFilePaths) {
+ ziggyCppPojo.setCppFilePaths(cppFilePaths);
+ }
+
+ public List getCppFilePaths() {
+ return ziggyCppPojo.getCppFilePaths();
+ }
+
+ // Paths for include files
+ public void setIncludeFilePaths(List extends Object> includeFilePaths) {
+ ziggyCppPojo.setIncludeFilePaths(includeFilePaths);
+ }
+
+ public List getIncludeFilePaths() {
+ return ziggyCppPojo.getIncludeFilePaths();
+ }
+
+ // paths for libraries that must be linked in
+ public void setLibraryPaths(List extends Object> libraryPaths) {
+ ziggyCppPojo.setLibraryPaths(libraryPaths);
+ }
+
+ public List getLibraryPaths() {
+ return ziggyCppPojo.getLibraryPaths();
+ }
+
+ // Libraries that must be linked in
+ public void setLibraries(List extends Object> libraries) {
+ ziggyCppPojo.setLibraries(libraries);
+ }
+
+ public List getLibraries() {
+ return ziggyCppPojo.getLibraries();
+ }
+
+ // compiler options
+ public void setCompileOptions(List extends Object> compileOptions) {
+ ziggyCppPojo.setCompileOptions(compileOptions);
+ ;
+ }
+
+ public List getCompileOptions() {
+ return ziggyCppPojo.getCompileOptions();
+ }
+
+ // linker options
+ public void setLinkOptions(List extends Object> linkOptions) {
+ ziggyCppPojo.setLinkOptions(linkOptions);
+ }
+
+ public List getLinkOptions() {
+ return ziggyCppPojo.getLinkOptions();
+ }
+
+ // output type (executable, shared library, static library)
+ public void setOutputType(Object outputType) {
+ ziggyCppPojo.setOutputType(outputType);
+ }
+
+ public BuildType getOutputType() {
+ return ziggyCppPojo.getOutputType();
+ }
+
+ // Name of file to be produced
+ public void setOutputName(Object name) {
+ ziggyCppPojo.setOutputName(name);
+ }
+
+ public String getOutputName() {
+ return ziggyCppPojo.getOutputName();
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMex.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMex.java
new file mode 100644
index 0000000..a29f4cd
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMex.java
@@ -0,0 +1,235 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.File;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.TaskAction;
+
+import gov.nasa.ziggy.buildutil.ZiggyCppPojo.BuildType;
+
+/**
+ * Performs mexfile builds for Gradle. The sequence of build steps is as follows:
+ * 1. The C/C++ files are compiled with a MATLAB_MEX_FILE compiler directive.
+ * 2. The object files from (1) are linked into a shared object library.
+ * 3. The actual mexfiles are built from the appropriate object files and the
+ * shared object library.
+ * The user specifies the following:
+ * 1. The path to the C/C++ files
+ * 2. Compiler and linker options, including libraries, library paths, include
+ * file paths, optimization flags.
+ * 3. The names of the desired mexfiles.
+ * 4. Optionally, a name for the shared library (otherwise a default name is generated
+ * from the C++ source file path).
+ *
+ * Because it is effectively impossible to unit test any class that extends DefaultTask,
+ * the actual workings of the ZiggyCppMex class are in a separate class, ZiggyCppMexPojo,
+ * which has appropriate unit testing. This class provides a thin interface between
+ * Gradle and the ZiggyCppMexPojo class.
+ *
+ * @author PT
+ *
+ */
+public class ZiggyCppMex extends DefaultTask {
+
+ private static final Logger log = LoggerFactory.getLogger(ZiggyCppMex.class);
+ private ZiggyCppMexPojo ziggyCppMexObject = new ZiggyCppMexPojo();
+
+ /**
+ * Default constructor. This constructor populates the 3 project directories needed by the
+ * ZiggyCppMexPojo class (build, root, and project), and populates the default compile and link
+ * options, and the default release and debug optimization options, if these are set as extra
+ * properties in the Project object.
+ */
+ public ZiggyCppMex() {
+ Project project = getProject();
+ ziggyCppMexObject.setBuildDir(project.getBuildDir());
+ ziggyCppMexObject.setRootDir(ZiggyCpp.pipelineRootDir(project));
+ ziggyCppMexObject.setProjectDir(project.getProjectDir());
+ if (project.hasProperty(ZiggyCppMexPojo.DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY)) {
+ ziggyCppMexObject.setCompileOptions(ZiggyCppPojo.gradlePropertyToList(
+ project.property(ZiggyCppMexPojo.DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppMexPojo.DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY)) {
+ ziggyCppMexObject.setLinkOptions(ZiggyCppPojo.gradlePropertyToList(
+ project.property(ZiggyCppMexPojo.DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppMexPojo.DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY)) {
+ ziggyCppMexObject.setReleaseOptimizations(ZiggyCppPojo.gradlePropertyToList(
+ project.findProperty(ZiggyCppMexPojo.DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY)));
+ }
+ if (project.hasProperty(ZiggyCppMexPojo.DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY)) {
+ ziggyCppMexObject.setDebugOptimizations(ZiggyCppPojo.gradlePropertyToList(
+ project.findProperty(ZiggyCppMexPojo.DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY)));
+ }
+ setMatlabPath();
+ }
+
+ /** Provides access to the ZiggyCppMexPojo method action() for Gradle. */
+ @TaskAction
+ public void action() {
+ ziggyCppMexObject.action();
+ }
+
+ /** Specifies that the C/C++ source files are the input files for this Gradle task. */
+ @InputFiles
+ public List getCppFiles() {
+ return ziggyCppMexObject.getCppFiles();
+ }
+
+ /** Specifies that the mexfiles are the output files for this Gradle task. */
+ @OutputFiles
+ public List getMexfiles() {
+ return ziggyCppMexObject.getMexfiles();
+ }
+
+ /** Specifies that the shared object library is also an output file for this Gradle task. */
+ @OutputFile
+ public File getBuiltFile() {
+ return ziggyCppMexObject.getBuiltFile();
+ }
+
+ // Below are setters and getters for the ZiggyCppMexPojo members that must be mutated
+ // by ZiggyCppMex tasks in gradle. In principle only setters are needed, but in the
+ // interest of sanity getters are also provided. Note that not all ZiggyCppMexPojo
+ // members need to be set by ZiggyCppMex, there are a number that are used internally
+ // and are not set as part of a task.
+ //
+ // Note that the setters take Object and List rather than String and List.
+ // This is because Gradle allows its text string objects to be either Java String class or
+ // Groovy GString class. Consequently, we pass everything to ZiggyCppMexPojo as Objects, and
+ // ZiggyCppMexPojo uses the toString() methods to convert everything to Java Strings.
+
+ // Path to the C++ source files
+ public void setCppFilePath(Object cppFilePath) {
+ ziggyCppMexObject.setCppFilePath(cppFilePath);
+ }
+
+ public String getCppFilePath() {
+ return ziggyCppMexObject.getCppFilePaths().get(0);
+ }
+
+ // Paths for include files
+ public void setIncludeFilePaths(List extends Object> includeFilePaths) {
+ ziggyCppMexObject.setIncludeFilePaths(includeFilePaths);
+ }
+
+ public List getIncludeFilePaths() {
+ return ziggyCppMexObject.getIncludeFilePaths();
+ }
+
+ // paths for libraries that must be linked in
+ public void setLibraryPaths(List extends Object> libraryPaths) {
+ ziggyCppMexObject.setLibraryPaths(libraryPaths);
+ }
+
+ public List getLibraryPaths() {
+ return ziggyCppMexObject.getLibraryPaths();
+ }
+
+ // Libraries that must be linked in
+ public void setLibraries(List extends Object> libraries) {
+ ziggyCppMexObject.setLibraries(libraries);
+ }
+
+ public List getLibraries() {
+ return ziggyCppMexObject.getLibraries();
+ }
+
+ // compiler options
+ public void setCompileOptions(List extends Object> compileOptions) {
+ ziggyCppMexObject.setCompileOptions(compileOptions);
+ ;
+ }
+
+ public List getCompileOptions() {
+ return ziggyCppMexObject.getCompileOptions();
+ }
+
+ // linker options
+ public void setLinkOptions(List extends Object> linkOptions) {
+ ziggyCppMexObject.setLinkOptions(linkOptions);
+ }
+
+ public List getLinkOptions() {
+ return ziggyCppMexObject.getLinkOptions();
+ }
+
+ // output type (executable, shared library, static library)
+ public void setOutputType(Object outputType) {
+ ziggyCppMexObject.setOutputType(outputType);
+ }
+
+ public BuildType getOutputType() {
+ return ziggyCppMexObject.getOutputType();
+ }
+
+ // Name of shared library file to be produced
+ public void setOutputName(Object name) {
+ ziggyCppMexObject.setOutputName(name);
+ }
+
+ public String getOutputName() {
+ return ziggyCppMexObject.getOutputName();
+ }
+
+ // Names of mexfiles to be produced
+ public void setMexfileNames(List extends Object> mexfileNames) {
+ ziggyCppMexObject.setMexfileNames(mexfileNames);
+ }
+
+ public List getMexfileNames() {
+ return ziggyCppMexObject.getMexfileNames();
+ }
+
+ /**
+ * Sets the path to the MATLAB executable. This searches the following options in the following
+ * order: 1. If there is a project extra property, matlabPath, use that. 2. If no matlabPath
+ * extra property, use the PATH environment variable to find the first path that includes both
+ * MATLAB (case-insensitive) and "bin" (case-sensitive). Use that. 3. If neither the path env
+ * var nor the project have the needed information, use the MATLAB_HOME env var. 4. If all of
+ * the above fail, throw a GradleException.
+ */
+ public void setMatlabPath() {
+ String matlabPath = null;
+ Project project = getProject();
+ if (project.hasProperty(ZiggyCppMexPojo.MATLAB_PATH_PROJECT_PROPERTY)) {
+ matlabPath = project.findProperty(ZiggyCppMexPojo.MATLAB_PATH_PROJECT_PROPERTY)
+ .toString();
+ log.info("MATLAB path set from project extra property: " + matlabPath);
+ }
+ if (matlabPath == null) {
+ String systemPath = System.getenv("PATH");
+ if (systemPath != null) {
+ String[] systemPaths = systemPath.split(":");
+ for (String path : systemPaths) {
+ String pathLower = path.toLowerCase();
+ if (pathLower.contains("matlab") && path.endsWith("bin")) {
+ matlabPath = path.substring(0, path.length() - 4);
+ log.info("MATLAB path set from PATH environment variable: " + matlabPath);
+ break;
+ }
+ }
+ }
+ }
+ if (matlabPath == null) {
+ String matlabHome = System.getenv(ZiggyCppMexPojo.MATLAB_PATH_ENV_VAR);
+ if (matlabHome != null) {
+ matlabPath = matlabHome;
+ log.info("MATLAB path set from MATLAB_HOME environment variable: " + matlabPath);
+ }
+ }
+ if (matlabPath == null) {
+ throw new GradleException(
+ "Unable to find MATLAB path in Gradle, PATH env var, or MATLAB_HOME env var");
+ }
+ ziggyCppMexObject.setMatlabPath(matlabPath);
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojo.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojo.java
new file mode 100644
index 0000000..d253ad8
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojo.java
@@ -0,0 +1,342 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.exec.DefaultExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.gradle.api.GradleException;
+import org.gradle.internal.os.OperatingSystem;
+
+/**
+ * Manages the construction of mexfiles from C/C++ source code. The source code is compiled
+ * using the C++ compiler in the CXX environment variable, with appropriate compiler options
+ * for use in creating object files that can be used in mexfiles. These object files are then
+ * combined into a shared library. Finally, the C++ compiler is used to produce mexfiles for
+ * each source file that contains the mexFunction entry point by linking the object files with
+ * the shared library and attaching an appropriate file type.
+ *
+ * Because Gradle task classes cannot easily be unit tested, the key functionality needed for
+ * mexfile construction is in this class; a separate class, ZiggyCppMex, extends the Gradle
+ * DefaultTask and provides the interface from Gradle to ZiggyCppMexPojo.
+ *
+ * @author PT
+ *
+ */
+public class ZiggyCppMexPojo extends ZiggyCppPojo {
+
+ private static final Logger log = LoggerFactory.getLogger(ZiggyCppMexPojo.class);
+
+ public static final String DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY = "defaultCppMexCompileOptions";
+ public static final String DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY = "defaultCppMexLinkOptions";
+ public static final String DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY = "defaultCppMexReleaseOptimizations";
+ public static final String DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY = "defaultCppMexDebugOptimizations";
+ public static final String MATLAB_PATH_PROJECT_PROPERTY = "matlabPath";
+ public static final String MATLAB_PATH_ENV_VAR = "MATLAB_HOME";
+
+ /** Path to the MATLAB directories to be used in the build */
+ private String matlabPath;
+
+ /** Names of the mexfiles that are to be built (these need to match names of C++ files) */
+ private List mexfileNames = null;
+
+ /** Files for the mexfiles that are to be built (used to determine the task inputs) */
+ private List mexfiles = null;
+
+ /** Project directory, used to generate a library file name */
+ private File projectDir = null;
+
+ public ZiggyCppMexPojo() {
+ super.setOutputType(BuildType.SHARED);
+ }
+
+ /**
+ * Returns the correct file type for a mexfile given the OS.
+ *
+ * @return string "mexmaci64" for a Mac, "mexa64" for Linux, GradleException for all other
+ * operating systems.
+ */
+ String mexSuffix() {
+ OperatingSystem os = getOperatingSystem();
+ String mexSuffix = null;
+ if (os.isMacOsX()) {
+ mexSuffix = "mexmaci64";
+ } else if (os.isLinux()) {
+ mexSuffix = "mexa64";
+ } else {
+ throw new GradleException("Operating system " + os.toString() + " not supported");
+ }
+ return mexSuffix;
+ }
+
+ /**
+ * Returns the correct MATLAB architecture name given the OS.
+ *
+ * @return string "maci64" for a Mac, "glnxa64" for Linux, Gradle exception for all other
+ */
+ String matlabArch() {
+ OperatingSystem os = getOperatingSystem();
+ String matlabArch = null;
+ if (os.isMacOsX()) {
+ matlabArch = "maci64";
+ } else if (os.isLinux()) {
+ matlabArch = "glnxa64";
+ } else {
+ throw new GradleException("Operating system " + os.toString() + " not supported");
+ }
+ return matlabArch;
+ }
+
+ /**
+ * Generates the mexfiles that are the output of this class, and stores them in the mexfiles
+ * list. The files that are generated are named $mexfileName.$mexfileSuffix, and are stored in
+ * $buildDir/lib .
+ */
+ void populateMexfiles() {
+ if (getBuildDir() == null || mexfileNames == null) {
+ throw new GradleException("buildDir and mexfileNames must not be null");
+ }
+ mexfiles = new ArrayList<>();
+ for (String mexfileName : mexfileNames) {
+ String fullMexfileName = mexfileName + "." + mexSuffix();
+ File mexfile = new File(libDir(), fullMexfileName);
+ mexfiles.add(mexfile);
+ }
+ }
+
+ /**
+ * Generates a Map between the mexfiles and their corresponding object files.
+ *
+ * @return HashMap from mexfiles to object files. If any mexfile is missing its corresponding
+ * object file, a GradleException is thrown.
+ */
+ private Map mapMexfilesToObjectFiles() {
+
+ // A linked hashmap is used to preserve order -- which doesn't matter so
+ // much for actual use (though it is convenient), but matters a lot for testing
+ Map mexfileMap = new LinkedHashMap<>();
+ List mexfiles = getMexfiles();
+ List objfiles = getObjectFiles();
+ for (String mexfileName : mexfileNames) {
+ File mexfile = getFileByName(mexfiles, mexfileName);
+ File objfile = getFileByName(objfiles, mexfileName);
+ if (objfile == null) {
+ throw new GradleException("No object file for mexfile " + mexfileName);
+ }
+ mexfileMap.put(mexfile, objfile);
+ }
+ return mexfileMap;
+ }
+
+ /**
+ * Finds the file out of a list of files that has a particular name when the file type is
+ * removed.
+ *
+ * @param files List of files
+ * @param fileName Name of desired match, assumed to have no file type attached to it.
+ * @return File with a name that contains the desired match, or null if no match is found.
+ */
+ private File getFileByName(List files, String fileName) {
+ File foundFile = null;
+ for (File file : files) {
+ String nameOfFile = file.getName();
+ int finalDot = nameOfFile.lastIndexOf('.');
+ String nameWithoutType = nameOfFile.substring(0, finalDot);
+ if (nameWithoutType.equals(fileName)) {
+ foundFile = file;
+ break;
+ }
+ }
+ return foundFile;
+ }
+
+ /**
+ * Generates the command to perform compilation of a source file. The command includes the
+ * MATLAB include directory as an include path, and includes the mexfile compiler directive.
+ */
+ @Override
+ public String generateCompileCommand(File sourceFile) {
+
+ // generate the include path
+ String matlabIncludePath = matlabPath + "/extern/include";
+ return generateCompileCommand(sourceFile, matlabIncludePath, "DMATLAB_MEX_FILE");
+ }
+
+ public String matlabLibPath() {
+ String matlabLibPath = matlabPath + "/bin/" + matlabArch();
+ return matlabLibPath;
+ }
+
+ @Override
+ protected void populateBuiltFile() {
+ if (getOutputName() == null || getOutputName().isEmpty()) {
+ setOutputName(generateSharedObjectName());
+ }
+ super.populateBuiltFile();
+ }
+
+ /**
+ * Generates the command to link the source files into a shared object. The commad includes the
+ * MATLAB library path and library names. If the user has not selected a name for the library,
+ * one will be generated from the project and C++ file paths.
+ */
+ @Override
+ public String generateLinkCommand() {
+
+ // if the build file is not set, then set it now to a default value
+ if (getOutputName() == null || getOutputName().isEmpty()) {
+ setOutputName(generateSharedObjectName());
+ }
+
+ // construct the path to the MATLAB shared object libraries
+ return generateLinkCommand(matlabLibPath());
+ }
+
+ /**
+ * Generates a name for the shared object library in the event that none has been set. This is
+ * done by taking the project name and adding to it the components of the C++ path name,
+ * separated by hyphens. For example, if the project directory is /path/to/pipeline/module1, and
+ * the source directory is /path/to/pipeline/module1/src/main/cpp/mex, the name of the library
+ * will be module1-src-main-cpp-mex, resulting in a shared object named
+ * libmodule1-src-main-cpp-mex.so or .dylib.
+ *
+ * @return Generated name for the shared object library.
+ */
+ public String generateSharedObjectName() {
+ String objectNameStart = getProjectDir().getName();
+ int projectDirLength = getProjectDir().getAbsolutePath().length();
+ String truncatedCppPath = getCppFilePaths().get(0).substring(projectDirLength + 1);
+ String[] truncatedCppPathParts = truncatedCppPath.split("/");
+ StringBuilder sharedObjectNameBuilder = new StringBuilder();
+ sharedObjectNameBuilder.append(objectNameStart + "-");
+ for (int i = 0; i < truncatedCppPathParts.length; i++) {
+ sharedObjectNameBuilder.append(truncatedCppPathParts[i]);
+ if (i < truncatedCppPathParts.length - 1) {
+ sharedObjectNameBuilder.append("-");
+ }
+ }
+ return sharedObjectNameBuilder.toString();
+ }
+
+ /**
+ * Generates the mex command for a given file.
+ *
+ * @param mexfile The desired mexfile output.
+ * @param obj The object file with the mexFunction entry point for the mexfile.
+ * @return A complete mex command in string form.
+ */
+ public String generateMexCommand(File mexfile, File obj) {
+
+ // if the build file is not set, then set it now to a default value
+ if (getOutputName() == null || getOutputName().isEmpty()) {
+ setOutputName(generateSharedObjectName());
+ }
+
+ StringBuilder mexCommandBuilder = new StringBuilder();
+ mexCommandBuilder.append(getCppCompiler() + " ");
+ mexCommandBuilder.append("-o " + mexfile.getAbsolutePath() + " ");
+ mexCommandBuilder.append(obj.getAbsolutePath() + " ");
+ mexCommandBuilder.append(argListToString(getLibraryPaths(), "-L"));
+ mexCommandBuilder.append("-L" + matlabLibPath() + " ");
+ mexCommandBuilder.append("-L" + libDir().getAbsolutePath() + " ");
+ mexCommandBuilder.append(argListToString(getLibraries(), "-l"));
+ mexCommandBuilder.append("-lmex -lmx -lmat ");
+ mexCommandBuilder.append("-l" + getOutputName() + " -shared");
+ return mexCommandBuilder.toString();
+ }
+
+ /**
+ * Main method used by Gradle. This method starts by using the ZiggyCppPojo action() method to
+ * compile the source files and build the shared library. The desired mexfiles are then looped
+ * over, and the mexfile commands are run by a DefaultExecutor.
+ */
+ @Override
+ public void action() {
+
+ log.info(String.format("%s.action()\n", this.getClass().getSimpleName()));
+
+ // Start by performing the compilation
+ compileAction();
+
+ // Map the mexfiles to their object files
+ Map mexfileMap = mapMexfilesToObjectFiles();
+
+ // remove the mexfile objects from the list of objects so they don't go into the
+ // shared object library
+ getObjectFiles().removeAll(mexfileMap.values());
+
+ // construct the shared object
+ linkAction();
+
+ // loop over mexfiles
+ for (File mexfile : mexfileMap.keySet()) {
+ String mexCommand = generateMexCommand(mexfile, mexfileMap.get(mexfile));
+ log.info(mexCommand);
+ DefaultExecutor mexExec = getDefaultExecutor();
+
+ // It's not strictly necessary to do this, since all the files have full
+ // paths, but it's a good practice nonetheless
+ mexExec.setWorkingDirectory(objDir());
+
+ // execute the mex command
+ try {
+ int returnCode = mexExec.execute(new CommandLineComparable(mexCommand));
+ if (returnCode != 0) {
+ throw new GradleException("Mexing of file " + mexfile.getName() + " failed");
+ }
+ } catch (IOException e) {
+ throw new GradleException("Mexing of file " + mexfile + " failed", e);
+ }
+ }
+ }
+
+ // Build type is not optional for the C++ Mex builds
+ @Override
+ public void setOutputType(BuildType buildType) {
+ log.warn("ZiggyCppMex does not support build types other than shared");
+ }
+
+ @Override
+ public void setOutputType(Object buildType) {
+ log.warn("ZiggyCppMex does not support build types other than shared");
+ }
+
+ // Setters and getters
+ public void setMexfileNames(List extends Object> mexfileNames) {
+ this.mexfileNames = new ArrayList<>();
+ this.mexfileNames.addAll(ZiggyCppPojo.objectListToStringList(mexfileNames));
+ }
+
+ public List getMexfileNames() {
+ return mexfileNames;
+ }
+
+ public void setMatlabPath(Object matlabPath) {
+ this.matlabPath = matlabPath.toString();
+ }
+
+ public String getMatlabPath() {
+ return matlabPath;
+ }
+
+ public List getMexfiles() {
+ if (mexfiles == null) {
+ populateMexfiles();
+ }
+ return mexfiles;
+ }
+
+ public File getProjectDir() {
+ return projectDir;
+ }
+
+ public void setProjectDir(File projectDir) {
+ this.projectDir = projectDir;
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppPojo.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppPojo.java
new file mode 100644
index 0000000..8a238bd
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyCppPojo.java
@@ -0,0 +1,759 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.gradle.api.GradleException;
+import org.gradle.internal.os.OperatingSystem;
+
+/**
+ * Performs compilation and linking of C++ code for Ziggy and for pipelines based on Ziggy.
+ * The command line options, source directory, include paths, library paths, library names, and
+ * type of build (executable, shared library, static library) and output file name
+ * must be specified in the Gradle task that makes use of this class. The compiler is determined
+ * from the CXX environment variable, thus is compatible with third-party packages that use the same
+ * convention. Actual source file names are deduced from listing the source directory, object file
+ * information is generated during the compile and saved for use in the link.
+ *
+ * If Gradle's JVM has cppdebug set to true as a system property, the compile and link commands will
+ * have appropriate options (-g and -Og). These will be placed after the compile / link options, thus
+ * will override any optimization options supplied to Gradle.
+ *
+ * NB: this is a POJO that makes minimal use of the Gradle API. In particular, it does not extend
+ * DefaultTask. This is because classes that extend DefaultTask are de facto impossible to unit test
+ * in Java. The ZiggyCpp class embeds a ZiggyCpp class to perform its actions and store its members.
+ *
+ * @author PT
+ *
+ */
+
+public class ZiggyCppPojo {
+
+ private static final Logger log = LoggerFactory.getLogger(ZiggyCppPojo.class);
+
+ /** Type of build: shared object, static library, or standalone program */
+ enum BuildType {
+ SHARED, STATIC, EXECUTABLE
+ }
+
+ public static final String CPP_COMPILER_ENV_VAR = "CXX";
+ public static final String[] CPP_FILE_TYPES = { ".c", ".cpp" };
+ public static final String CPP_DEBUG_PROPERTY_NAME = "cppdebug";
+
+ public static final String DEFAULT_COMPILE_OPTIONS_GRADLE_PROPERTY = "defaultCppCompileOptions";
+ public static final String DEFAULT_LINK_OPTIONS_GRADLE_PROPERTY = "defaultCppLinkOptions";
+ public static final String DEFAULT_RELEASE_OPTS_GRADLE_PROPERTY = "defaultCppReleaseOptimizations";
+ public static final String DEFAULT_DEBUG_OPTS_GRADLE_PROPERTY = "defaultCppDebugOptimizations";
+ public static final String PIPELINE_ROOT_DIR_PROP_NAME = "pipelineRootDir";
+
+ /** Path to the C++ files to be compiled */
+ private List cppFilePaths = null;
+
+ /** Paths to the include files */
+ private List includeFilePaths = new ArrayList<>();
+
+ /** Paths to the libraries needed in linking */
+ private List libraryPaths = new ArrayList<>();
+
+ /** Libraries needed for linking (minus the "lib" prefix and all file type suffixes) */
+ private List libraries = new ArrayList<>();
+
+ /** compile options (minus the initial hyphen) */
+ private List compileOptions = new ArrayList<>();
+
+ /** linker options (minus the initial hyphen) */
+ private List linkOptions = new ArrayList<>();
+
+ /** Optimizations, if any, desired for a build without cppdebug=true system property */
+ private List releaseOptimizations = new ArrayList<>();
+
+ /** Optimizations, if any, desired for a build with cppdebug=true system property */
+ private List debugOptimizations = new ArrayList<>();
+
+ /** Caller-selected build type */
+ private BuildType outputType = null;
+
+ /** Name of the output file (with no "lib" prefix or file type suffix) */
+ private String name = null;
+
+ /** C++ files found in the cppFilePath directory */
+ private List cppFiles = new ArrayList<>();
+
+ /** Object files built from the C++ files */
+ private List objectFiles = new ArrayList<>();
+
+ /** Desired output (executable or library) as a File */
+ private File builtFile = null;
+
+ /** Desired Gradle build directory, as a File */
+ private File buildDir = null;
+
+ /** Root directory for the parent Gradle project, as a File */
+ private File rootDir = null;
+
+ /** C++ compiler command including path to same */
+ private String cppCompiler = null;
+
+ /** Default executor used only for testing, do not use for real execution! */
+ private DefaultExecutor defaultExecutor = null;
+
+ /** Operating system, needed to set options and names for the linker command */
+ private OperatingSystem operatingSystem = OperatingSystem.current();
+
+ // stores logger warning messages. Used only for testing.
+ private List loggerWarnings = new ArrayList<>();
+
+ /**
+ * Converts a list of arguments to a single string that can be used in a command line compiler
+ * call
+ *
+ * @param argList list of arguments
+ * @param prefix prefix for each argument ("-I", "-L", etc.)
+ * @return the list of arguments converted to a string, and with the prefix added to each
+ */
+ public String argListToString(List argList, String prefix) {
+ StringBuilder argStringBuilder = new StringBuilder();
+ for (String arg : argList) {
+ argStringBuilder.append(prefix + arg + " ");
+ }
+ return argStringBuilder.toString();
+ }
+
+ File objDir() {
+ return new File(buildDir, "obj");
+ }
+
+ File libDir() {
+ return new File(buildDir, "lib");
+ }
+
+ File binDir() {
+ return new File(buildDir, "bin");
+ }
+
+ /**
+ * Search the specified file path for C and C++ files, and populate the cppFiles list with same.
+ * If the file path is not set or does not exist, a GradleException will be thrown.
+ */
+ private void populateCppFiles() {
+
+ // check that the path is set and exists
+ if (cppFilePaths == null) {
+ throw new GradleException("C++ file path is null");
+ }
+
+ // clear any existing files, and also handle the null pointer case
+ // neither of these should ever occur in real life, but why risk it?
+ if (cppFiles == null || !cppFiles.isEmpty()) {
+ cppFiles = new ArrayList<>();
+ }
+
+ for (String cppFilePath : cppFilePaths) {
+ File cppFileDir = new File(cppFilePath);
+ if (!cppFileDir.exists()) {
+ String w = "C++ file path " + cppFilePath + " does not exist";
+ log.warn(w);
+ addLoggerWarning(w);
+
+ } else {
+
+ // find all C and C++ files and add them to the cppFiles list
+ for (String fileType : CPP_FILE_TYPES) {
+ File[] cFiles = cppFileDir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(fileType);
+ }
+ });
+ for (File file : cFiles) {
+ cppFiles.add(file);
+ }
+ }
+ }
+ }
+
+ if (!cppFiles.isEmpty()) {
+ Collections.sort(cppFiles);
+ // write the list of files to the log if info logging is set
+ StringBuilder fileListBuilder = new StringBuilder();
+ for (File file : cppFiles) {
+ fileListBuilder.append(file.getName());
+ fileListBuilder.append(" ");
+ }
+ log.info("List of C/C++ files in directory " + cppFilePaths + ": "
+ + fileListBuilder.toString());
+ }
+ }
+
+ /**
+ * Populate the builtFile member based on the name of the file to be built, its type, and the
+ * OS. Throws a GradleException if the name isn't defined, the output type isn't defined, or the
+ * OS is' something other than Mac OS, Linux, or Unix.
+ */
+ protected void populateBuiltFile() {
+
+ // handle error cases
+ if (name == null || outputType == null) {
+ throw new GradleException("Both output name and output type must be specified");
+ }
+
+ String outputDirectory = null;
+ String prefix = null;
+ String fileType = null;
+ // determine output directory
+ if (outputType == BuildType.EXECUTABLE) {
+ outputDirectory = binDir().getAbsolutePath();
+ prefix = "";
+ fileType = "";
+ } else {
+ outputDirectory = libDir().getAbsolutePath();
+ prefix = "lib";
+ if (outputType == BuildType.STATIC) {
+ fileType = ".a";
+ } else {
+ if (operatingSystem.isMacOsX()) {
+ fileType = ".dylib";
+ } else if (operatingSystem.isLinux()) {
+ fileType = ".so";
+ } else {
+ throw new GradleException(
+ "ZiggyCpp class does not support OS " + operatingSystem.getName());
+ }
+ }
+ }
+ String outputFile = prefix + name + fileType;
+ builtFile = new File(outputDirectory, outputFile);
+
+ }
+
+ /**
+ * Determines the name of the object file that is generated when compiling a given source file.
+ *
+ * @param sourceFile source C/C++ file
+ * @return name of the source file with original type stripped off and replaced with ".o"
+ */
+ public static String objectNameFromSourceFile(File sourceFile) {
+ String sourceName = sourceFile.getName();
+ String strippedName = null;
+ for (String fileType : CPP_FILE_TYPES) {
+ if (sourceName.endsWith(fileType)) {
+ strippedName = sourceName.substring(0, sourceName.length() - fileType.length());
+ break;
+ }
+ }
+ return strippedName + ".o";
+ }
+
+ /**
+ * Generates the command to compile a single source file
+ *
+ * @param sourceFile File of the C/C++ source that is to be compiled
+ * @return the compile command as a single string. This command will include the include files
+ * and command line options specified in the object, and will route the output to the correct
+ * output directory (specifically $buildDir/obj). It will also take care of setting options
+ * correctly for a debug build if the JVM has cppdebug=true set as a system property.
+ */
+ public String generateCompileCommand(File sourceFile) {
+ return generateCompileCommand(sourceFile, null, null);
+ }
+
+ /**
+ * Generates the command to compile a single source file, with additional options that are
+ * needed for mexfiles
+ *
+ * @param sourceFile File of the C/C++ source that is to be compiled
+ * @param matlabIncludePath String that indicates the location of MATLAB include files, can be
+ * null
+ * @param matlabCompilerDirective String that contains the MATLAB compiler directive, can be
+ * null
+ * @return the compile command as a single string. This command will include the include files
+ * and command line options specified in the object, and will route the output to the correct
+ * output directory (specifically $buildDir/obj). It will also take care of setting options
+ * correctly for a debug build if the JVM has cppdebug=true set as a system property.
+ */
+ public String generateCompileCommand(File sourceFile, String matlabIncludePath,
+ String matlabCompilerDirective) {
+
+ StringBuilder compileStringBuilder = new StringBuilder();
+
+ // compiler executable
+ compileStringBuilder.append(getCppCompiler() + " ");
+
+ // compile only flag
+ compileStringBuilder.append("-c ");
+
+ // define the output file
+ compileStringBuilder.append(
+ "-o " + objDir().getAbsolutePath() + "/" + objectNameFromSourceFile(sourceFile) + " ");
+
+ // add the include paths
+ compileStringBuilder.append(argListToString(includeFilePaths, "-I"));
+
+ // If there is a MATLAB include path, handle that now
+ if (matlabIncludePath != null && !matlabIncludePath.isEmpty()) {
+ compileStringBuilder.append("-I" + matlabIncludePath + " ");
+ }
+
+ // add the command line options
+ compileStringBuilder.append(argListToString(compileOptions, "-"));
+
+ // if there is a MATLAB compiler directive, handle that now
+ if (matlabCompilerDirective != null && !matlabCompilerDirective.isEmpty()) {
+ compileStringBuilder.append("-" + matlabCompilerDirective + " ");
+ }
+
+ // depending on whether there is a cppdebug system property set to true, we either set up
+ // for debugging, or -- not.
+ boolean debug = false;
+ if (System.getProperty(CPP_DEBUG_PROPERTY_NAME) != null) {
+ debug = Boolean.getBoolean(CPP_DEBUG_PROPERTY_NAME);
+ }
+ if (debug) {
+ compileStringBuilder.append(argListToString(debugOptimizations, "-"));
+ } else {
+ compileStringBuilder.append(argListToString(releaseOptimizations, "-"));
+ }
+ compileStringBuilder.append(sourceFile.getAbsolutePath());
+
+ // send the results of this to the log if info mode is selected
+ log.info(compileStringBuilder.toString());
+
+ return compileStringBuilder.toString();
+
+ }
+
+ /**
+ * Generates a linker command line. The line takes into account the linker options, the desired
+ * output type (executable, static library, or shared object), and library paths and names.
+ *
+ * @return Linker command line as a String.
+ */
+ public String generateLinkCommand() {
+ return generateLinkCommand(null);
+ }
+
+ /**
+ * Generates a linker command line. The line takes into account the linker options, the desired
+ * output type (executable, static library, or shared object), and library paths and names.
+ *
+ * @param matlabLibPath String that indicates the path to MATLAB shared objects, can be null
+ * @return Linker command line as a String.
+ */
+ public String generateLinkCommand(String matlabLibPath) {
+
+ StringBuilder linkStringBuilder = new StringBuilder();
+
+ // start with the actual command, which is either the compiler or the archive builder
+ if (outputType == BuildType.STATIC) {
+ linkStringBuilder.append("ar rs ");
+ } else {
+ linkStringBuilder.append(getCppCompiler() + " -o ");
+ }
+
+ // add the name of the desired output file
+ linkStringBuilder.append(getBuiltFile().getAbsolutePath() + " ");
+
+ // if this is an executable or shared object, add the linker options
+ // and library paths
+ if (outputType != BuildType.STATIC) {
+ linkStringBuilder.append(argListToString(libraryPaths, "-L"));
+ if (matlabLibPath != null && !matlabLibPath.isEmpty()) {
+ linkStringBuilder.append("-L" + matlabLibPath + " ");
+ }
+
+ }
+
+ // add release or debug options
+ if (outputType == BuildType.EXECUTABLE) {
+ linkStringBuilder.append(argListToString(linkOptions, "-"));
+ if (System.getProperty(CPP_DEBUG_PROPERTY_NAME) != null
+ && Boolean.getBoolean(CPP_DEBUG_PROPERTY_NAME)) {
+ linkStringBuilder.append(argListToString(debugOptimizations, "-"));
+ } else {
+ linkStringBuilder.append(argListToString(releaseOptimizations, "-"));
+ }
+ }
+
+ // if this is to be a shared object, put in the "-shared" option
+
+ if (outputType == BuildType.SHARED) {
+ linkStringBuilder.append("-shared ");
+ }
+
+ // if the OS is Mac OS, set the install name. The install name assumes that the library
+ // will be installed in the build/lib directory under the root directory.
+
+ if (operatingSystem.isMacOsX() && outputType == BuildType.SHARED) {
+ linkStringBuilder.append("-install_name " + getRootDir().getAbsolutePath()
+ + "/build/lib/" + getBuiltFile().getName() + " ");
+ }
+
+ // add the object files
+ for (File objectFile : objectFiles) {
+ linkStringBuilder.append(objectFile.getName() + " ");
+ }
+
+ // Add library names. These have come after the object files due to a positional
+ // dependence in the Linux linker
+ if (outputType != BuildType.STATIC) {
+ linkStringBuilder.append(argListToString(libraries, "-l"));
+ if (matlabLibPath != null && !matlabLibPath.isEmpty()) {
+ linkStringBuilder.append("-lmex -lmx -lmat ");
+ }
+ }
+
+ log.info(linkStringBuilder.toString());
+ return linkStringBuilder.toString();
+
+ }
+
+ /**
+ * Returns a DefaultExecutor object. For normal execution, this always returns a new object, but
+ * if the defaultExecutor member is non-null, that is what is returned. This latter case should
+ * only happen in testing, when a mocked DefaultExecutor object is stored in defaultExecutor.
+ *
+ * @return a new DefaultExecutor (normal execution), or a mocked one (testing).
+ */
+ protected DefaultExecutor getDefaultExecutor() {
+ if (defaultExecutor == null) {
+ return new DefaultExecutor();
+ }
+ return defaultExecutor;
+ }
+
+ /**
+ * Main action of the class. This method compiles the files and captures information about the
+ * resulting object files, then performs whatever linking / library building action is required.
+ * If any compile or the final link / library build command fails, a GradleException is thrown.
+ * Files in the include directories that end in .h or .hpp are copied to the build directory's
+ * include subdir.
+ */
+ public void action() {
+
+ log.info(String.format("%s.action()\n", this.getClass().getSimpleName()));
+
+ // compile the source files
+ compileAction();
+
+ // perform the linker / archiver step
+ linkAction();
+ }
+
+ protected void compileAction() {
+
+ // create the obj directory
+
+ File objDir = objDir();
+ if (!objDir.exists()) {
+ log.info("mkdir: " + objDir.getAbsolutePath());
+ objDir.mkdirs();
+ }
+ // loop over source files, compile them and add the object file to the object file list
+ for (File file : getCppFiles()) {
+ DefaultExecutor compilerExec = getDefaultExecutor();
+ compilerExec.setWorkingDirectory(new File(cppFilePaths.get(0)));
+ try {
+ int returnCode = compilerExec
+ .execute(new CommandLineComparable(generateCompileCommand(file)));
+
+ if (returnCode != 0) {
+ throw new GradleException("Compilation of file " + file.getName() + " failed");
+ }
+ objectFiles.add(new File(objDir, objectNameFromSourceFile(file)));
+ } catch (IOException e) {
+ throw new GradleException(
+ "IOException occurred when attempting to compile " + file.getName(), e);
+ }
+ }
+ }
+
+ protected void linkAction() {
+
+ File objDir = objDir();
+ DefaultExecutor linkExec = getDefaultExecutor();
+ linkExec.setWorkingDirectory(objDir);
+ File destDir = null;
+ if (outputType.equals(BuildType.EXECUTABLE)) {
+ destDir = binDir();
+ } else {
+ destDir = libDir();
+ }
+ if (!destDir.exists()) {
+ log.info("mkdir: " + destDir.getAbsolutePath());
+ destDir.mkdirs();
+ }
+ try {
+ int returnCode = linkExec.execute(new CommandLineComparable(generateLinkCommand()));
+ if (returnCode != 0) {
+ throw new GradleException(
+ "Link / library construction of " + getBuiltFile().getName() + " failed");
+ }
+ } catch (IOException e) {
+ throw new GradleException("IOException occurred during link / library construction of "
+ + getBuiltFile().getName(), e);
+ }
+
+ // copy the files from each of the include directories to buildDir/include
+ File includeDest = new File(buildDir, "include");
+ for (String include : includeFilePaths) {
+ File[] includeFiles = new File(include).listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return (name.endsWith(".h") || name.endsWith(".hpp"));
+ }
+ });
+ for (File includeFile : includeFiles) {
+ try {
+ FileUtils.copyFileToDirectory(includeFile, includeDest);
+ } catch (IOException e) {
+ throw new GradleException("Unable to copy include files from" + include + " to "
+ + includeDest.getAbsoluteFile(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Converts a list of Objects to a list of Strings, preserving their order.
+ *
+ * @param libraries2 List of objects to be converted.
+ * @return ArrayList of strings obtained by taking toString() of the objects in the objectList.
+ */
+ static List objectListToStringList(List extends Object> libraries2) {
+ List stringList = new ArrayList<>();
+ for (Object obj : libraries2) {
+ stringList.add(obj.toString());
+ }
+ return stringList;
+ }
+
+ /**
+ * Converts a Gradle property to a list of Strings. The property can be a scalar or a list, Java
+ * Strings or Groovy GStrings.
+ *
+ * @param gradleProperty property to be converted.
+ * @return contents of gradleProperty as a list of Java Strings.
+ */
+ @SuppressWarnings("unchecked")
+ static List gradlePropertyToList(Object gradleProperty) {
+ if (gradleProperty instanceof List>) {
+ return objectListToStringList((List extends Object>) gradleProperty);
+ } else {
+ List gradlePropertyList = new ArrayList<>();
+ gradlePropertyList.add(gradleProperty);
+ return objectListToStringList((List extends Object>) gradlePropertyList);
+ }
+ }
+
+// setters and getters
+
+ public void setCppFilePath(Object cppFilePath) {
+ this.cppFilePaths = new ArrayList<>();
+ cppFilePaths.add(cppFilePath.toString());
+ }
+
+ public List getCppFilePaths() {
+ return cppFilePaths;
+ }
+
+ public void setCppFilePaths(List cppFilePaths) {
+ this.cppFilePaths = objectListToStringList(cppFilePaths);
+ }
+
+ public List getIncludeFilePaths() {
+ return includeFilePaths;
+ }
+
+ public void setIncludeFilePaths(List extends Object> includeFilePaths) {
+ this.includeFilePaths = new ArrayList<>();
+ this.includeFilePaths.addAll(objectListToStringList(includeFilePaths));
+ }
+
+ public List getLibraryPaths() {
+ return libraryPaths;
+ }
+
+ public void setLibraryPaths(List extends Object> libraryPaths) {
+ this.libraryPaths = new ArrayList<>();
+ this.libraryPaths.addAll(objectListToStringList(libraryPaths));
+ }
+
+ public List getLibraries() {
+ return libraries;
+ }
+
+ public void setLibraries(List extends Object> libraries) {
+ this.libraries = new ArrayList<>();
+ this.libraries.addAll(objectListToStringList(libraries));
+ }
+
+ public List getCompileOptions() {
+ return compileOptions;
+ }
+
+ public void setCompileOptions(List extends Object> compileOptions) {
+ this.compileOptions = new ArrayList<>();
+ this.compileOptions.addAll(objectListToStringList(compileOptions));
+ }
+
+ public List getLinkOptions() {
+ return linkOptions;
+ }
+
+ public void setLinkOptions(List extends Object> linkOptions) {
+ this.linkOptions = new ArrayList<>();
+ this.linkOptions.addAll(objectListToStringList(linkOptions));
+ }
+
+ public List getReleaseOptimizations() {
+ return releaseOptimizations;
+ }
+
+ public void setReleaseOptimizations(List extends Object> releaseOptimizations) {
+ this.releaseOptimizations = new ArrayList<>();
+ this.releaseOptimizations.addAll(objectListToStringList(releaseOptimizations));
+ }
+
+ public List getDebugOptimizations() {
+ return debugOptimizations;
+ }
+
+ public void setDebugOptimizations(List extends Object> debugOptimizations) {
+ this.debugOptimizations = new ArrayList<>();
+ this.debugOptimizations.addAll(objectListToStringList(debugOptimizations));
+ }
+
+ public BuildType getOutputType() {
+ return outputType;
+ }
+
+ public void setOutputType(BuildType outputType) {
+ this.outputType = outputType;
+ }
+
+ public void setOutputType(Object outputType) {
+ this.outputType = BuildType.valueOf(outputType.toString().toUpperCase());
+ }
+
+ public String getOutputName() {
+ return name;
+ }
+
+ public void setOutputName(Object name) {
+ this.name = name.toString();
+ }
+
+ public List getCppFiles() {
+ // always generate the list afresh -- necessary because Gradle calls the ZiggyCpp
+ // method getCppFiles() prior to the actual build, at which time the directories of
+ // source files may or may not exist yet! Thus we can't afford to cache the C++
+ // file list, since I can't tell whether Gradle creates a new ZiggyCpp object when
+ // it actually does the build, or whether it simply re-uses the one from pre-build.
+ populateCppFiles();
+ return cppFiles;
+ }
+
+ public List getObjectFiles() {
+ return objectFiles;
+ }
+
+ public void setObjectFiles(List objectFiles) {
+ this.objectFiles.addAll(objectFiles);
+ }
+
+ public void setObjectFiles(File objectFile) {
+ this.objectFiles.add(objectFile);
+ }
+
+ public File getBuiltFile() {
+ if (builtFile == null) {
+ populateBuiltFile();
+ }
+ return builtFile;
+ }
+
+ public File getBuildDir() {
+ return buildDir;
+ }
+
+ public void setBuildDir(File buildDir) {
+ this.buildDir = buildDir;
+ }
+
+ public File getRootDir() {
+ return rootDir;
+ }
+
+ public void setRootDir(File rootDir) {
+ this.rootDir = rootDir;
+ }
+
+ public String getCppCompiler() {
+ if (cppCompiler == null) {
+ cppCompiler = System.getenv(CPP_COMPILER_ENV_VAR);
+ }
+ return cppCompiler;
+ }
+
+ public OperatingSystem getOperatingSystem() {
+ return operatingSystem;
+ }
+
+ // this method is intended for use only in testing, for that reason it is package-private
+ void setCppCompiler(String cppCompiler) {
+ this.cppCompiler = cppCompiler;
+ }
+
+ // this method is intended for use only in testing, for that reason it is package-private
+ void setDefaultExecutor(DefaultExecutor defaultExecutor) {
+ this.defaultExecutor = defaultExecutor;
+ }
+
+ // this method is intended for use only in testing, for that reason it is package-private
+ void setOperatingSystem(OperatingSystem operatingSystem) {
+ this.operatingSystem = operatingSystem;
+ }
+
+ /**
+ * Thin wrapper for Apache Commons CommandLine class that provides an equals() method. This is
+ * needed for unit testing, since Mockito checks argument agreements using the argument's
+ * equals() method, and CommandLine doesn't have one.
+ *
+ * @author PT
+ */
+ class CommandLineComparable extends CommandLine {
+
+ public CommandLineComparable(String executable) {
+ super(CommandLine.parse(executable));
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof CommandLine) {
+ CommandLine oo = (CommandLine) o;
+ if (this.toString().contentEquals(oo.toString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ // add a logger warning to the list of same. Used only for testing.
+ private void addLoggerWarning(String warning) {
+ loggerWarnings.add(warning);
+ }
+
+ // retrieve the list of saved logger warnings. Used only for testing.
+ List loggerWarnings() {
+ return loggerWarnings;
+ }
+}
diff --git a/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyVersionGenerator.java b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyVersionGenerator.java
new file mode 100644
index 0000000..3f8afc2
--- /dev/null
+++ b/buildSrc/src/main/java/gov/nasa/ziggy/buildutil/ZiggyVersionGenerator.java
@@ -0,0 +1,188 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import com.google.common.collect.ImmutableList;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generates version info in the generated file ZiggyVersion.java$ git rev-list --all --abbrev=0 --abbrev-commit | awk '{print length()}' | sort -n | uniq -c
+ * 1040 4
+ * 7000 5
+ * 1149 6
+ * 68 7
+ * 8 8
+ * 1 9
+ *
+ *
+ */
+public class ZiggyVersionGenerator extends TessExecTask {
+
+ private static final Logger log = LoggerFactory.getLogger(ZiggyVersionGenerator.class);
+ private static final String MAC_OS_X_OS_NAME = "Mac OS X";
+
+ public File outputFile;
+ public final String dateFormat = "dd-MMM-yyyy HH:mm:ss";
+ private String osType;
+
+ public void generateFile(BufferedWriter out) throws IOException, InterruptedException {
+
+ osType = System.getProperty("os.name");
+ log.debug("OS Type: " + osType);
+ Configuration config = new Configuration();
+ config.setClassForTemplateLoading(this.getClass(), "/");
+ config.setDefaultEncoding("UTF-8");
+ config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+
+ VersionInfo versionInfo = new VersionInfo();
+ versionInfo.setBuildDate(getBuildDate());
+ versionInfo.setSoftwareVersion(getGitRelease());
+ versionInfo.setBranch(getGitBranch());
+ versionInfo.setRevision(getGitRevision());
+
+ try {
+ config.getTemplate("ZiggyVersion.java.ftlh").process(versionInfo, out);
+ } catch (TemplateException e) {
+ throw new IllegalStateException("Error processing template", e);
+ }
+ }
+
+ public List getProcessOutput(List command)
+ throws IOException, InterruptedException {
+
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ Process process = processBuilder.start();
+ List lines = new ArrayList<>();
+
+ BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()));
+
+ for (;;) {
+ String line = bufferedReader.readLine();
+ if (line == null) {
+ break;
+ }
+
+ lines.add(line);
+ }
+
+ process.waitFor();
+ return lines;
+ }
+
+ public String getGitRevision() throws IOException, InterruptedException {
+ if (osType.equals(MAC_OS_X_OS_NAME)) {
+ return "Not Supported";
+ }
+ List cmd = ImmutableList.of("git", "rev-parse", "--short=10", "HEAD");
+ List output = getProcessOutput(cmd);
+ return output.get(output.size() - 1);
+ }
+
+ public String getGitBranch() throws IOException, InterruptedException {
+ if (osType.equals(MAC_OS_X_OS_NAME)) {
+ return "Not Supported";
+ }
+ List cmd = ImmutableList.of("git", "rev-parse", "--abbrev-ref", "HEAD");
+ List output = getProcessOutput(cmd);
+ return output.get(output.size() - 1);
+ }
+
+ public String getGitRelease() throws IOException, InterruptedException {
+ if (osType.equals(MAC_OS_X_OS_NAME)) {
+ return "Not Supported";
+ }
+ List cmd = ImmutableList.of("git", "describe", "--always", "--abbrev=10");
+ List output = getProcessOutput(cmd);
+ return output.get(output.size() - 1);
+ }
+
+ public String getBuildDate() {
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat);
+ return dateFormatter.format(new Date());
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(File output) {
+ outputFile = output;
+ }
+
+ @TaskAction
+ public void action() throws IOException, InterruptedException {
+ try (BufferedWriter output = new BufferedWriter(new FileWriter(outputFile))) {
+ generateFile(output);
+ }
+ }
+
+ /**
+ * Holds version information in a Java bean suitable for referencing from a template.
+ */
+ public static class VersionInfo {
+
+ private String buildDate;
+ private String softwareVersion;
+ private String revision;
+ private String branch;
+
+ public String getBuildDate() {
+ return buildDate;
+ }
+
+ public void setBuildDate(String dateStr) {
+ buildDate = dateStr;
+ }
+
+ public String getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public void setSoftwareVersion(String versionStr) {
+ softwareVersion = versionStr;
+ }
+
+ public String getRevision() {
+ return revision;
+ }
+
+ public void setRevision(String revision) {
+ this.revision = revision;
+ }
+
+ public String getBranch() {
+ return branch;
+ }
+
+ public void setBranch(String branch) {
+ this.branch = branch;
+ }
+ }
+}
diff --git a/buildSrc/src/main/resources/ZiggyVersion.java.ftlh b/buildSrc/src/main/resources/ZiggyVersion.java.ftlh
new file mode 100644
index 0000000..da7da6c
--- /dev/null
+++ b/buildSrc/src/main/resources/ZiggyVersion.java.ftlh
@@ -0,0 +1,88 @@
+<#-- Template for generating ZiggyVersion.java -->
+package gov.nasa.ziggy.util;
+
+import java.text.SimpleDateFormat;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides code versioning information.
+ *
+ * This has been automatically generated! Do not edit.
+ */
+public class ZiggyVersion {
+
+ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
+
+ private static final Pattern NON_TAG_PATTERN = Pattern.compile("^.*-g[A-Fa-f0-9]+$");
+
+ /**
+ * Gets the build date.
+ *
+ * @return the date and time this file was created, as a Java date
+ * @throws IllegalStateException if there is an error interpreting the build date string
+ */
+ public static Date getBuildDate() {
+ try {
+ return DATE_FORMAT.parse("${buildDate}");
+ } catch (ParseException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Gets the software revision. The format will vary by revision control
+ * system. For Git repositories, this should be generated by "git describe".
+ *
+ * @return the software version, as a string
+ */
+ public static String getSoftwareVersion() {
+ return "${softwareVersion}";
+ }
+
+ /**
+ * Gets the latest version control commit revision identifier. For Git repositories, this
+ * is the commit hash.
+ *
+ * @return the latest commit revision identifier, as a string
+ */
+ public static String getRevision() {
+ return "${revision}";
+ }
+
+ /**
+ * Tests whether the software revision corresponds to a release. In TESS,
+ * we are running a release if it was compiled from a release branch.
+ *
+ * @return true, if the software revision is a released version
+ */
+ public static boolean isRelease() {
+ return getBranch().startsWith("releases/") || (atTag() && getBranch().equals("HEAD"));
+ }
+
+ /**
+ * Gets the branch of the software revision.
+ *
+ * @return the branch used to build this file
+ */
+ public static String getBranch() {
+ return "${branch}";
+ }
+
+ /**
+ * Tests whether we have been checked out from a tag. If so, we
+ * will be in a detached head state, in which case the software
+ * revision obtained by "git describe" will not have a trailing
+ * "{@code -g}".
+ *
+ * @return true, if we have been checked out from a tag, false otherwise
+ */
+ private static boolean atTag() {
+ Matcher matcher = NON_TAG_PATTERN.matcher(getSoftwareVersion());
+ return !matcher.matches();
+ }
+
+}
diff --git a/buildSrc/src/main/sh/macosx-sdk-selection.sh b/buildSrc/src/main/sh/macosx-sdk-selection.sh
new file mode 100644
index 0000000..1c0ba64
--- /dev/null
+++ b/buildSrc/src/main/sh/macosx-sdk-selection.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+
+# Get the current OS version number
+
+macosx_version=`uname -r`
+
+# depending on the version number, we need to set the SDK-related variables differently:
+
+case $macosx_version in
+ 14*)
+ SDKROOT='/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/'
+ MACOSX_DEPLOYMENT_TARGET='10.10'
+ ;;
+ 13*)
+ SDKROOT='/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/'
+ MACOSX_DEPLOYMENT_TARGET='10.9'
+ ;;
+ 12*)
+ SDKROOT='/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/'
+ MACOSX_DEPLOYMENT_TARGET='10.8'
+ ;;
+ 11*)
+ SDKROOT='/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/'
+ MACOSX_DEPLOYMENT_TARGET='10.7'
+ ;;
+ *)
+ SDKROOT='/Developer/SDKs/MacOSX10.6.sdk'
+ MACOSX_DEPLOYMENT_TARGET='10.6'
+ ;;
+esac
diff --git a/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojoTest.java b/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojoTest.java
new file mode 100644
index 0000000..206ab0a
--- /dev/null
+++ b/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppMexPojoTest.java
@@ -0,0 +1,432 @@
+package gov.nasa.ziggy.buildutil;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.GradleException;
+import org.gradle.internal.os.OperatingSystem;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import gov.nasa.ziggy.buildutil.ZiggyCppPojo.BuildType;
+
+public class ZiggyCppMexPojoTest {
+
+ File tempDir = null;
+ File buildDir = null;
+ File rootDir = null;
+ File projectDir = null;
+ File srcDir = null;
+ File incDir = null;
+ ZiggyCppMexPojo ziggyCppMexObject = null;
+
+ DefaultExecutor defaultExecutor = Mockito.mock(DefaultExecutor.class);
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Before
+ public void before() throws IOException {
+
+ // create a temporary directory for everything
+ tempDir = Files.createTempDirectory("rootDir").toFile();
+ tempDir.deleteOnExit();
+
+ // rootDir is the same as tempDir
+ rootDir = tempDir;
+
+ // projectDir
+ projectDir = new File(rootDir,"projectDir");
+ projectDir.mkdir();
+
+ // build directory under project
+ buildDir = new File(projectDir, "build");
+ buildDir.mkdir();
+
+ // lib, bin, obj, and include directories under build
+ new File(buildDir, "lib").mkdir();
+ new File(buildDir, "obj").mkdir();
+ new File(buildDir, "bin").mkdir();
+ new File(buildDir, "include").mkdir();
+
+ // add a source directory that's several levels down
+ srcDir = new File(projectDir, "src/main/cpp/mex");
+ srcDir.mkdirs();
+
+ // add an include directory that's several levels down
+ incDir = new File(projectDir, "src/main/include");
+ incDir.mkdirs();
+
+ // create source files
+ createSourceFiles();
+
+ // create the ZiggyCppMexPojo object
+ ziggyCppMexObject = createZiggyCppMexPojo();
+ }
+
+ @After
+ public void after() throws IOException {
+
+ // explicitly delete the temp directory
+ FileUtils.deleteDirectory(tempDir);
+
+ // delete any cppdebug system properties
+ System.clearProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME);
+
+ // delete the ZiggyCpp object
+ ziggyCppMexObject = null;
+ buildDir = null;
+ tempDir = null;
+ projectDir = null;
+ srcDir = null;
+ incDir = null;
+ }
+
+//***************************************************************************************
+
+ // Here begins the actual test classes
+
+ /** Tests that the output type setters have no effect on the output type
+ *
+ */
+ @Test
+ public void testOutputTypeSetters() {
+ ziggyCppMexObject.setOutputType("executable");
+ assertEquals(ziggyCppMexObject.getOutputType(), BuildType.SHARED);
+ ziggyCppMexObject.setOutputType("static");
+ assertEquals(ziggyCppMexObject.getOutputType(), BuildType.SHARED);
+ ziggyCppMexObject.setOutputType(BuildType.EXECUTABLE);
+ assertEquals(ziggyCppMexObject.getOutputType(), BuildType.SHARED);
+ ziggyCppMexObject.setOutputType(BuildType.STATIC);
+ assertEquals(ziggyCppMexObject.getOutputType(), BuildType.SHARED);
+ }
+
+ /**
+ * Tests the setters and getters that are unique to the ZiggyCppMexPojo (the ones
+ * that are inherited from ZiggyCppPojo are not tested).
+ */
+ @Test
+ public void testSettersAndGetters() {
+
+ // these getter tests implicitly test the setters in createZiggyCppMexPojo():
+ assertEquals(projectDir.getAbsolutePath(), ziggyCppMexObject.getProjectDir().getAbsolutePath());
+ assertEquals("/dev/null/MATLAB_R2017b", ziggyCppMexObject.getMatlabPath());
+ List mexfileNames = ziggyCppMexObject.getMexfileNames();
+ assertEquals(2, mexfileNames.size());
+ assertEquals("CSource1", mexfileNames.get(0));
+ assertEquals("CppSource2", mexfileNames.get(1));
+ }
+
+ /**
+ * Tests the compile command generator, in particular to make certain that the MATLAB include
+ * path and MATLAB_MEX_FILE compiler directive are present
+ */
+ @Test
+ public void testGenerateCompileCommand() {
+
+ // test with debug options disabled
+ String compileString = ziggyCppMexObject.generateCompileCommand(new File("/dev/null/dmy1.c"));
+ String expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/dmy1.o "
+ + "-I" + srcDir.getAbsolutePath() + " -I" + incDir.getAbsolutePath()
+ + " -I/dev/null/MATLAB_R2017b/extern/include -Wall -fPic -DMATLAB_MEX_FILE -O2 -DNDEBUG -g "
+ + "/dev/null/dmy1.c";
+ assertEquals(expectedString, compileString);
+
+ // test with debug options enabled
+ System.setProperty("cppdebug", "true");
+ compileString = ziggyCppMexObject.generateCompileCommand(new File("/dev/null/dmy1.c"));
+ expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/dmy1.o "
+ + "-I" + srcDir.getAbsolutePath() + " -I" + incDir.getAbsolutePath()
+ + " -I/dev/null/MATLAB_R2017b/extern/include -Wall -fPic -DMATLAB_MEX_FILE -Og -g "
+ + "/dev/null/dmy1.c";
+ assertEquals(expectedString, compileString);
+
+ // test with debug property present but set to false
+ System.setProperty("cppdebug", "false");
+ compileString = ziggyCppMexObject.generateCompileCommand(new File("/dev/null/dmy1.c"));
+ expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/dmy1.o "
+ + "-I" + srcDir.getAbsolutePath() + " -I" + incDir.getAbsolutePath()
+ + " -I/dev/null/MATLAB_R2017b/extern/include -Wall -fPic -DMATLAB_MEX_FILE -O2 -DNDEBUG -g "
+ + "/dev/null/dmy1.c";
+ assertEquals(expectedString, compileString);
+ }
+
+ @Test
+ public void testGenerateSharedObjectName() {
+ String generatedName = ziggyCppMexObject.generateSharedObjectName();
+ assertEquals("projectDir-src-main-cpp-mex", generatedName);
+ }
+
+ @Test
+ public void testGenerateLinkCommand() {
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ String linkCommand = ziggyCppMexObject.generateLinkCommand();
+ String expectedCommand = "/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/"
+ + "libdummy.so -L/dummy1/lib -L/dummy2/lib "
+ +"-L/dev/null/MATLAB_R2017b/bin/glnxa64 -shared o1.o o2.o -lhdf5 -lnetcdf -lmex -lmx -lmat ";
+ assertEquals(expectedCommand, linkCommand);
+
+ // now test the library name for empty object name
+ ziggyCppMexObject = createZiggyCppMexPojo();
+ ziggyCppMexObject.setOutputName("");
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ linkCommand = ziggyCppMexObject.generateLinkCommand();
+ expectedCommand = "/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/"
+ + "libprojectDir-src-main-cpp-mex.so -L/dummy1/lib -L/dummy2/lib "
+ +"-L/dev/null/MATLAB_R2017b/bin/glnxa64 -shared o1.o o2.o -lhdf5 -lnetcdf -lmex -lmx -lmat ";
+ assertEquals(expectedCommand, linkCommand);
+
+ // test for Mac OS
+ ziggyCppMexObject = createZiggyCppMexPojo();
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.MAC_OS);
+ linkCommand = ziggyCppMexObject.generateLinkCommand();
+ expectedCommand = "/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/"
+ + "libdummy.dylib -L/dummy1/lib -L/dummy2/lib "
+ +"-L/dev/null/MATLAB_R2017b/bin/maci64 "
+ +"-shared -install_name " + rootDir.getAbsolutePath()+"/build/lib/libdummy.dylib"
+ + " o1.o o2.o -lhdf5 -lnetcdf -lmex -lmx -lmat ";
+ assertEquals(expectedCommand, linkCommand);
+ }
+
+ @Test
+ public void testGenerateMexCommand() {
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ configureLinkerOptions(ziggyCppMexObject);
+ File mexfile = new File(buildDir, "lib/o1.mexmaci64");
+ File objFile = new File(buildDir, "obj/o1.o");
+ String mexCommand = ziggyCppMexObject.generateMexCommand(mexfile, objFile);
+ String expectedCommand = "/dev/null/g++ -o " + mexfile.getAbsolutePath() + " "
+ + objFile.getAbsolutePath() + " -L/dummy1/lib -L/dummy2/lib "
+ + "-L/dev/null/MATLAB_R2017b/bin/glnxa64 -L" + buildDir.getAbsolutePath() + "/lib "
+ + "-lhdf5 -lnetcdf -lmex -lmx -lmat -ldummy -shared";
+ assertEquals(expectedCommand, mexCommand);
+
+ // test for empty library object name
+ ziggyCppMexObject = createZiggyCppMexPojo();
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOutputName("");
+ mexCommand = ziggyCppMexObject.generateMexCommand(mexfile, objFile);
+ expectedCommand = "/dev/null/g++ -o " + mexfile.getAbsolutePath() + " "
+ + objFile.getAbsolutePath() + " -L/dummy1/lib -L/dummy2/lib "
+ + "-L/dev/null/MATLAB_R2017b/bin/glnxa64 -L" + buildDir.getAbsolutePath() + "/lib "
+ + "-lhdf5 -lnetcdf -lmex -lmx -lmat -lprojectDir-src-main-cpp-mex -shared";
+ assertEquals(expectedCommand, mexCommand);
+
+ // test for Mac OS
+ ziggyCppMexObject = createZiggyCppMexPojo();
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.MAC_OS);
+ mexCommand = ziggyCppMexObject.generateMexCommand(mexfile, objFile);
+ expectedCommand = "/dev/null/g++ -o " + mexfile.getAbsolutePath() + " "
+ + objFile.getAbsolutePath() + " -L/dummy1/lib -L/dummy2/lib "
+ + "-L/dev/null/MATLAB_R2017b/bin/maci64 -L" + buildDir.getAbsolutePath() + "/lib "
+ + "-lhdf5 -lnetcdf -lmex -lmx -lmat -ldummy -shared";
+ assertEquals(expectedCommand, mexCommand);
+ }
+
+ @Test
+ public void testAction() throws ExecuteException, IOException {
+
+ // set the mocked executor into the object
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ ziggyCppMexObject.setDefaultExecutor(defaultExecutor);
+ InOrder executorCalls = Mockito.inOrder(defaultExecutor);
+
+ // call the method
+ ziggyCppMexObject.action();
+
+ // check the calls -- first the 4 compile commands
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(projectDir,
+ "src/main/cpp/mex"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateCompileCommand(new File(srcDir, "CSource1.c"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(projectDir,
+ "src/main/cpp/mex"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateCompileCommand(new File(srcDir, "CSource2.c"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(projectDir,
+ "src/main/cpp/mex"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateCompileCommand(new File(srcDir, "CppSource1.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(projectDir,
+ "src/main/cpp/mex"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateCompileCommand(new File(srcDir, "CppSource2.cpp"))));
+
+ // then the link command for the dynamic library (and also make sure that 2 of the 4 files
+ // got removed from the list of object files)
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir,
+ "obj"));
+ List allObjectFiles = ziggyCppMexObject.getObjectFiles();
+ assertEquals(2, allObjectFiles.size());
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateLinkCommand()));
+
+ // then the mex commands
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir,
+ "obj"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateMexCommand(new File(buildDir, "lib/CSource1.mexa64"),
+ new File(buildDir, "obj/CSource1.o"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir,
+ "obj"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppMexObject.new CommandLineComparable(
+ ziggyCppMexObject.generateMexCommand(new File(buildDir, "lib/CppSource2.mexa64"),
+ new File(buildDir, "obj/CppSource2.o"))));
+
+ }
+
+ // Here are unit tests that exercise various error cases
+
+ @SuppressWarnings("serial")
+ @Test
+ public void testErrorMexfileMissingSourceFile() {
+ ziggyCppMexObject.setMexfileNames(new ArrayList() {{
+ add("CSource3");
+ }});
+ ziggyCppMexObject.setDefaultExecutor(defaultExecutor);
+ exception.expect(GradleException.class);
+ exception.expectMessage("No object file for mexfile CSource3");
+ ziggyCppMexObject.action();
+ }
+
+ @Test
+ public void testErrorMexReturnCode() throws ExecuteException, IOException {
+ ziggyCppMexObject.setDefaultExecutor(defaultExecutor);
+ configureLinkerOptions(ziggyCppMexObject);
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.LINUX);
+ File mexfile = new File(buildDir, "lib/CSource1.mexa64");
+ File objFile = new File(buildDir, "obj/CSource1.o");
+ String mexCommand = ziggyCppMexObject.generateMexCommand(mexfile, objFile);
+ when(defaultExecutor.execute(ziggyCppMexObject.new CommandLineComparable(
+ mexCommand))).thenReturn(1);
+ exception.expect(GradleException.class);
+ exception.expectMessage("Mexing of file CSource1.mexa64 failed");
+ ziggyCppMexObject.action();
+ }
+
+ @Test
+ public void testBadMexSuffix() {
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.WINDOWS);
+ exception.expect(GradleException.class);
+ ziggyCppMexObject.mexSuffix();
+ }
+
+ @Test
+ public void testBadMatlabArch() {
+ ziggyCppMexObject.setOperatingSystem(OperatingSystem.WINDOWS);
+ exception.expect(GradleException.class);
+ ziggyCppMexObject.matlabArch();
+ }
+
+ @SuppressWarnings("serial")
+ @Test
+ public void testNoBuildDir() {
+ ZiggyCppMexPojo ziggyCppMexObject = new ZiggyCppMexPojo();
+ ziggyCppMexObject.setMexfileNames(new ArrayList() {{
+ add("CSource1");
+ add("CppSource2");
+ }});
+ exception.expect(GradleException.class);
+ exception.expectMessage("buildDir and mexfileNames must not be null");
+ ziggyCppMexObject.populateMexfiles();
+ }
+
+ @Test
+ public void testNoMexfiles() {
+ ZiggyCppMexPojo ziggyCppMexObject = new ZiggyCppMexPojo();
+ ziggyCppMexObject.setBuildDir(buildDir);
+ exception.expect(GradleException.class);
+ exception.expectMessage("buildDir and mexfileNames must not be null");
+ ziggyCppMexObject.populateMexfiles();
+ }
+
+//***************************************************************************************
+
+ // here begins assorted setup and helper methods
+
+ public void createSourceFiles() throws IOException {
+
+ // create 4 temporary "C/C++" files in the source directory
+ new File(srcDir, "CSource1.c").createNewFile();
+ new File(srcDir, "CSource2.c").createNewFile();
+ new File(srcDir, "CppSource1.cpp").createNewFile();
+ new File(srcDir, "CppSource2.cpp").createNewFile();
+
+ new File(srcDir, "Header1.h").createNewFile();
+ new File(incDir, "Header2.hpp").createNewFile();
+ }
+
+ @SuppressWarnings("serial")
+ public ZiggyCppMexPojo createZiggyCppMexPojo() {
+ ZiggyCppMexPojo ziggyCppMexObject = new ZiggyCppMexPojo();
+ ziggyCppMexObject.setBuildDir(buildDir);
+ ziggyCppMexObject.setProjectDir(projectDir);
+ ziggyCppMexObject.setRootDir(rootDir);
+ ziggyCppMexObject.setCppCompiler("/dev/null/g++");
+ ziggyCppMexObject.setCppFilePath(srcDir.getAbsolutePath());
+ ziggyCppMexObject.setMatlabPath("/dev/null/MATLAB_R2017b");
+ ziggyCppMexObject.setOutputName("dummy");
+ ziggyCppMexObject.setMexfileNames(new ArrayList() {{
+ add("CSource1");
+ add("CppSource2");
+ }});
+ ziggyCppMexObject.setIncludeFilePaths(new ArrayList() {{
+ add(srcDir.getAbsolutePath());
+ add(incDir.getAbsolutePath());
+ }});
+ ziggyCppMexObject.setCompileOptions(new ArrayList() {{
+ add("Wall");
+ add("fPic");
+ }});
+ ziggyCppMexObject.setReleaseOptimizations(new ArrayList() {{
+ add("O2");
+ add("DNDEBUG");
+ add("g");
+ }});
+ ziggyCppMexObject.setDebugOptimizations(new ArrayList() {{
+ add("Og");
+ add("g");
+ }});
+ return ziggyCppMexObject;
+ }
+
+ public void configureLinkerOptions(ZiggyCppPojo ziggyCppObject) {
+ // first we need to add some object files
+ File o1 = new File(buildDir, "obj/o1.o");
+ File o2 = new File(buildDir, "obj/o2.o");
+ ziggyCppObject.setObjectFiles(o1);
+ ziggyCppObject.setObjectFiles(o2);
+
+ // also some linker options and libraries
+ List linkerOptions = new ArrayList<>();
+ linkerOptions.add("u whatevs");
+ ziggyCppObject.setLinkOptions(linkerOptions);
+ List libraryPathOptions = new ArrayList<>();
+ libraryPathOptions.add("/dummy1/lib");
+ libraryPathOptions.add("/dummy2/lib");
+ ziggyCppObject.setLibraryPaths(libraryPathOptions);
+ List libraryOptions = new ArrayList<>();
+ libraryOptions.add("hdf5");
+ libraryOptions.add("netcdf");
+ ziggyCppObject.setLibraries(libraryOptions);
+ }
+}
diff --git a/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppPojoTest.java b/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppPojoTest.java
new file mode 100644
index 0000000..daa7d05
--- /dev/null
+++ b/buildSrc/src/test/java/gov/nasa/ziggy/buildutil/ZiggyCppPojoTest.java
@@ -0,0 +1,764 @@
+package gov.nasa.ziggy.buildutil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.GradleException;
+import org.gradle.internal.os.OperatingSystem;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import gov.nasa.ziggy.buildutil.ZiggyCppPojo;
+import gov.nasa.ziggy.buildutil.ZiggyCppPojo.BuildType;
+
+/**
+ * Unit test class for ZiggyCppPojo class.
+ * @author PT
+ *
+ */
+public class ZiggyCppPojoTest {
+
+ File tempDir = null;
+ File buildDir = null;
+ File srcDir = null;
+ File rootDir = new File("/dev/null/rootDir");
+ ZiggyCppPojo ziggyCppObject;
+
+ DefaultExecutor defaultExecutor = Mockito.mock(DefaultExecutor.class);
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Before
+ public void before() throws IOException {
+
+ // create a temporary directory for everything
+ tempDir = Files.createTempDirectory("ZiggyCpp").toFile();
+ tempDir.deleteOnExit();
+
+ // directory for includes
+ new File(tempDir, "include").mkdir();
+
+ // directory for source
+ srcDir = new File(tempDir, "src");
+ srcDir.mkdir();
+
+ // build directory
+ buildDir = new File(tempDir,"build");
+
+ // directory for libraries
+ new File(buildDir,"lib").mkdir();
+
+ // directory for includes
+ new File(buildDir, "include").mkdir();
+
+ // directory for built source
+ new File(buildDir, "src").mkdir();
+
+ // directory for objects
+ new File(buildDir, "obj").mkdir();
+
+ // directory for executables
+ new File(buildDir, "bin").mkdir();
+
+ // create C++ source and header files
+ createSourceFiles();
+
+ // create the ZiggyCpp object
+ ziggyCppObject = createZiggyCppObject(buildDir);
+
+ }
+
+ @After
+ public void after() throws IOException {
+
+ // explicitly delete the temp directory
+ FileUtils.deleteDirectory(tempDir);
+
+ // delete any cppdebug system properties
+ System.clearProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME);
+
+ // delete the ZiggyCpp object
+ ziggyCppObject = null;
+ buildDir = null;
+ tempDir = null;
+ }
+
+//***************************************************************************************
+
+ // here begins the test methods
+
+ /**
+ * Tests all getter and setter methods.
+ * @throws InvocationTargetException
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ * @throws NoSuchMethodException
+ */
+ @Test
+ public void getterSetterTest() throws NoSuchMethodException, IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException {
+
+ List dummyArguments = new ArrayList<>();
+ dummyArguments.add("DuMMY");
+ dummyArguments.add("dUmMy");
+ assertEquals(tempDir.getAbsolutePath() + "/src", ziggyCppObject.getCppFilePaths().get(0));
+
+ testStringListSettersAndGetters("IncludeFilePaths", new String[] {
+ tempDir.getAbsolutePath() + "/src",
+ tempDir.getAbsolutePath() + "/include"});
+ testStringListSettersAndGetters("CompileOptions", new String[] {
+ "Wall", "fPic"});
+ testStringListSettersAndGetters("ReleaseOptimizations", new String[] {
+ "O2", "DNDEBUG", "g"});
+ testStringListSettersAndGetters("DebugOptimizations", new String[] {
+ "Og", "g"});
+ ziggyCppObject.setLibraries(dummyArguments);
+ testStringListSettersAndGetters("Libraries", new String[]{"DuMMY", "dUmMy"});
+ ziggyCppObject.setLibraryPaths(dummyArguments);
+ testStringListSettersAndGetters("LibraryPaths", new String[]{"DuMMY", "dUmMy"});
+ ziggyCppObject.setLinkOptions(dummyArguments);
+ testStringListSettersAndGetters("LinkOptions", new String[]{"DuMMY", "dUmMy"});
+
+ ziggyCppObject.setOutputName("outputName");
+ assertEquals("outputName", ziggyCppObject.getOutputName());
+
+ ziggyCppObject.setOutputType(BuildType.EXECUTABLE);
+ assertEquals(BuildType.EXECUTABLE, ziggyCppObject.getOutputType());
+ ziggyCppObject.setOutputType("executable");
+ assertEquals(BuildType.EXECUTABLE, ziggyCppObject.getOutputType());
+
+ ziggyCppObject.setOutputType(BuildType.SHARED);
+ assertEquals(BuildType.SHARED, ziggyCppObject.getOutputType());
+ ziggyCppObject.setOutputType("shared");
+ assertEquals(BuildType.SHARED, ziggyCppObject.getOutputType());
+
+ ziggyCppObject.setOutputType(BuildType.STATIC);
+ assertEquals(BuildType.STATIC, ziggyCppObject.getOutputType());
+ ziggyCppObject.setOutputType("static");
+ assertEquals(BuildType.STATIC, ziggyCppObject.getOutputType());
+
+ assertEquals(buildDir, ziggyCppObject.getBuildDir());
+ assertEquals(buildDir.getAbsolutePath(), ziggyCppObject.getBuildDir().getAbsolutePath());
+
+ }
+
+ /**
+ * Tests the ability to find C/C++ source files in the source directory and add them to
+ * the ZiggyCppPojo as a list of File objects
+ */
+ @Test
+ public void testGetCppFiles() {
+ List cppFiles = ziggyCppObject.getCppFiles();
+ assertEquals(2, cppFiles.size());
+ List cppFilePaths = cppFiles.stream().map(s -> s.getAbsolutePath())
+ .collect(Collectors.toList());
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/ZiggyCppMain.cpp"));
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/GetString.cpp"));
+
+ cppFiles = ziggyCppObject.getCppFiles();
+ assertEquals(2, cppFiles.size());
+ cppFilePaths = cppFiles.stream().map(s -> s.getAbsolutePath())
+ .collect(Collectors.toList());
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/ZiggyCppMain.cpp"));
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/GetString.cpp"));
+ }
+
+ @Test
+ public void testGetCppFilesMultipleDirectories() throws FileNotFoundException {
+
+ // put a source directory in build, and populate it
+ new File(buildDir, "src/cpp").mkdirs();
+ createAdditionalSource();
+
+ // create the list of directories to check out
+ List cppPaths = new ArrayList<>();
+ cppPaths.add(tempDir.toString() + "/src");
+ cppPaths.add(buildDir.toString() + "/src/cpp");
+ ziggyCppObject.setCppFilePaths(cppPaths);
+ List cppFiles = ziggyCppObject.getCppFiles();
+ int nFiles = cppFiles.size();
+ assertEquals(3, nFiles);
+
+ List cppFilePaths = cppFiles.stream().map(s -> s.getAbsolutePath())
+ .collect(Collectors.toList());
+ assertTrue(cppFilePaths.contains(buildDir.getAbsolutePath() + "/src/cpp/GetAnotherString.cpp"));
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/ZiggyCppMain.cpp"));
+ assertTrue(cppFilePaths.contains(tempDir.getAbsolutePath() + "/src/GetString.cpp"));
+ }
+
+ /**
+ * Tests the argListToString method, which converts a list of arguments to a string, with a common
+ * prefix added to each list element
+ */
+ @Test
+ public void argListToStringTest() {
+ String compileOptionString = ziggyCppObject.argListToString(ziggyCppObject.getCompileOptions(),
+ "-");
+ assertEquals("-Wall -fPic ", compileOptionString);
+ }
+
+ /**
+ * Tests the code that determines the File that is to be the output of the compile and link
+ * process.
+ */
+ @Test
+ public void populateBuiltFileTest() {
+
+ // executable
+ ziggyCppObject.setOutputType("executable");
+ File builtFile = ziggyCppObject.getBuiltFile();
+ assertEquals(buildDir.getAbsolutePath() + "/bin/dummy", builtFile.getAbsolutePath());
+
+ // shared library
+ ZiggyCppPojo ziggyCppShared = createZiggyCppObject(buildDir);
+ ziggyCppShared.setOutputType("shared");
+ ziggyCppShared.setOperatingSystem(OperatingSystem.MAC_OS);
+ builtFile = ziggyCppShared.getBuiltFile();
+ String builtFilePath = builtFile.getAbsolutePath();
+ String sharedObjectFileType = ".dylib";
+ assertEquals(buildDir.getAbsolutePath() + "/lib/libdummy" + sharedObjectFileType, builtFilePath);
+ ziggyCppShared = createZiggyCppObject(buildDir);
+ ziggyCppShared.setOutputType("shared");
+ ziggyCppShared.setOperatingSystem(OperatingSystem.LINUX);
+ builtFile = ziggyCppShared.getBuiltFile();
+ builtFilePath = builtFile.getAbsolutePath();
+ sharedObjectFileType = ".so";
+ assertEquals(buildDir.getAbsolutePath() + "/lib/libdummy" + sharedObjectFileType, builtFilePath);
+
+ // static library
+ ZiggyCppPojo ziggyCppStatic = createZiggyCppObject(buildDir);
+ ziggyCppStatic.setOutputType("static");
+ builtFile = ziggyCppStatic.getBuiltFile();
+ builtFilePath = builtFile.getAbsolutePath();
+ assertEquals(buildDir.getAbsolutePath() + "/lib/libdummy.a", builtFilePath);
+ }
+
+ /**
+ * Tests the process of converting a source File to an object file name (with the
+ * path of the former stripped away).
+ */
+ @Test
+ public void objectNameFromSourceFileTest() {
+ File f1 = new File("/tmp/dummy/s1.c");
+ String s1 = ZiggyCppPojo.objectNameFromSourceFile(f1);
+ assertEquals("s1.o", s1);
+ File f2 = new File("/tmp/dummy/s1.cpp");
+ String s2 = ZiggyCppPojo.objectNameFromSourceFile(f2);
+ assertEquals("s1.o", s2);
+ }
+
+ /**
+ * Tests the method that generates compile commands.
+ */
+ @Test
+ public void generateCompileCommandTest() {
+ File f1 = new File("/tmp/dummy/s1.c");
+ String compileCommand = ziggyCppObject.generateCompileCommand(f1);
+ String expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/s1.o "
+ + "-I" + tempDir.getAbsolutePath() + "/src -I" + tempDir.getAbsolutePath() +
+ "/include -Wall -fPic -O2 -DNDEBUG -g /tmp/dummy/s1.c";
+ assertEquals(expectedString, compileCommand);
+
+ // set up for debugging
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "true");
+ compileCommand = ziggyCppObject.generateCompileCommand(f1);
+ expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/s1.o "
+ + "-I" + tempDir.getAbsolutePath() + "/src -I" + tempDir.getAbsolutePath() +
+ "/include -Wall -fPic -Og -g /tmp/dummy/s1.c";
+ assertEquals(expectedString, compileCommand);
+
+ // have the debugging property but set to false
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "false");
+ compileCommand = ziggyCppObject.generateCompileCommand(f1);
+ expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/s1.o "
+ + "-I" + tempDir.getAbsolutePath() + "/src -I" + tempDir.getAbsolutePath() +
+ "/include -Wall -fPic -O2 -DNDEBUG -g /tmp/dummy/s1.c";
+ assertEquals(expectedString, compileCommand);
+
+ // test .cpp file type
+ f1 = new File("/tmp/dummy/s1.cpp");
+ compileCommand = ziggyCppObject.generateCompileCommand(f1);
+ expectedString = "/dev/null/g++ -c -o " + buildDir.getAbsolutePath() + "/obj/s1.o "
+ + "-I" + tempDir.getAbsolutePath() + "/src -I" + tempDir.getAbsolutePath() +
+ "/include -Wall -fPic -O2 -DNDEBUG -g /tmp/dummy/s1.cpp";
+ assertEquals(expectedString, compileCommand);
+ }
+
+ /**
+ * Tests the method that generates link commands.
+ */
+ @Test
+ public void generateLinkCommandTest() {
+
+ configureLinkerOptions(ziggyCppObject);
+ ziggyCppObject.setOutputType("executable");
+ String linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/bin/dummy -L/dummy1/lib -L/dummy2/lib "
+ + "-u whatevs -O2 -DNDEBUG -g o1.o o2.o -lhdf5 -lnetcdf ", linkString);
+
+ // now try it with debug enabled
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "true");
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/bin/dummy -L/dummy1/lib -L/dummy2/lib "
+ + "-u whatevs -Og -g o1.o o2.o -lhdf5 -lnetcdf ", linkString);
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "false");
+
+
+ // Now for a shared object library
+ ziggyCppObject = createZiggyCppObject(buildDir);
+ configureLinkerOptions(ziggyCppObject);
+ ziggyCppObject.setOutputType("shared");
+ ziggyCppObject.setOperatingSystem(OperatingSystem.LINUX);
+ String sharedObjectFileType = ".so";
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/libdummy" + sharedObjectFileType
+ + " -L/dummy1/lib -L/dummy2/lib -shared"
+ + " o1.o o2.o -lhdf5 -lnetcdf ", linkString);
+
+ // For a Mac, there has to be an install name as well
+ ziggyCppObject = createZiggyCppObject(buildDir);
+ configureLinkerOptions(ziggyCppObject);
+ ziggyCppObject.setOutputType("shared");
+ ziggyCppObject.setOperatingSystem(OperatingSystem.MAC_OS);
+ sharedObjectFileType = ".dylib";
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/libdummy" + sharedObjectFileType
+ + " -L/dummy1/lib -L/dummy2/lib -shared"
+ + " -install_name /dev/null/rootDir/build/lib/libdummy.dylib "
+ + "o1.o o2.o -lhdf5 -lnetcdf ", linkString);
+
+ // debug enabled shouldn't do anything
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "true");
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("/dev/null/g++ -o " + buildDir.getAbsolutePath() + "/lib/libdummy" + sharedObjectFileType
+ + " -L/dummy1/lib -L/dummy2/lib -shared"
+ + " -install_name /dev/null/rootDir/build/lib/libdummy.dylib "
+ + "o1.o o2.o -lhdf5 -lnetcdf ", linkString);
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "false");
+
+ // static library case
+ ziggyCppObject = createZiggyCppObject(buildDir);
+ configureLinkerOptions(ziggyCppObject);
+ ziggyCppObject.setOutputType("static");
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("ar rs " + buildDir.getAbsolutePath() + "/lib/libdummy.a o1.o o2.o ", linkString);
+
+ // debug enabled shouldn't do anything
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "true");
+ linkString = ziggyCppObject.generateLinkCommand();
+ assertEquals("ar rs " + buildDir.getAbsolutePath() + "/lib/libdummy.a o1.o o2.o ", linkString);
+ System.setProperty(ZiggyCppPojo.CPP_DEBUG_PROPERTY_NAME, "false");
+
+ }
+
+ /**
+ * Tests the method that executes the main action (compiles and links).
+ * @throws ExecuteException
+ * @throws IOException
+ */
+ @Test
+ public void actionTest() throws ExecuteException, IOException {
+
+ // set values for the ZiggyCppPojo
+ ziggyCppObject.setOutputName("testOutput");
+ ziggyCppObject.setOutputType("executable");
+
+ // set the mocked executor into the object
+ ziggyCppObject.setDefaultExecutor(defaultExecutor);
+ InOrder executorCalls = Mockito.inOrder(defaultExecutor);
+
+ // call the method
+ ziggyCppObject.action();
+
+ // check the calls to the executor and their order
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "GetString.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "ZiggyCppMain.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir, "obj"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateLinkCommand()));
+
+ // test that the include files were copied
+ File buildInclude = new File(buildDir, "include");
+ File buildInclude1 = new File(buildInclude, "ZiggyCppMain.h");
+ assertTrue(buildInclude1.exists());
+ File buildInclude2 = new File(buildInclude, "ZiggyCppLib.h");
+ assertTrue(buildInclude2.exists());
+
+
+ // create a new object for linking a shared object
+ ziggyCppObject = createZiggyCppObject(buildDir);
+ ziggyCppObject.setOutputName("testOutput");
+ ziggyCppObject.setOutputType("shared");
+ ziggyCppObject.setDefaultExecutor(defaultExecutor);
+ ziggyCppObject.action();
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "GetString.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "ZiggyCppMain.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir, "obj"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateLinkCommand()));
+
+ // and once more for a static library
+ // create a new object for linking a shared object
+ ziggyCppObject = createZiggyCppObject(buildDir);
+ ziggyCppObject.setOutputName("testOutput");
+ ziggyCppObject.setOutputType("static");
+ ziggyCppObject.setDefaultExecutor(defaultExecutor);
+ ziggyCppObject.action();
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "GetString.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(tempDir, "src"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateCompileCommand(new File(srcDir, "ZiggyCppMain.cpp"))));
+ executorCalls.verify(defaultExecutor).setWorkingDirectory(new File(buildDir, "obj"));
+ executorCalls.verify(defaultExecutor).execute(ziggyCppObject.new CommandLineComparable(
+ ziggyCppObject.generateLinkCommand()));
+
+ }
+
+ // The following tests exercise various error conditions that return GradleExceptions. There are 2 error
+ // conditions that are not tested by these methods, they occur when the DefaultExecutor throws an IOException.
+ // These cases are considered sufficiently trivial that we omit them.
+
+ /**
+ * Tests that a null value for the C++ file path produces the correct error.
+ */
+ @Test
+ public void testCppFilePathNullError() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ exception.expect(GradleException.class);
+ exception.expectMessage("C++ file path is null");
+ ziggyCppError.getCppFiles();
+ }
+
+ /**
+ * Tests that a nonexistent C++ file path produces the correct warning message.
+ */
+ @Test
+ public void testCppFilePathDoesNotExist() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ ziggyCppError.setCppFilePath("/this/path/does/not/exist");
+ ziggyCppError.getCppFiles();
+ List w = ziggyCppError.loggerWarnings();
+ assertTrue(w.size() > 0);
+ assertTrue(w.contains("C++ file path /this/path/does/not/exist does not exist"));
+ }
+
+ /**
+ * Tests that a missing output name produces the correct error.
+ */
+ @Test
+ public void testErrorNoOutputName() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ ziggyCppError.setCppFilePath(tempDir.getAbsolutePath() + "/src");
+ ziggyCppError.setBuildDir(buildDir);
+ ziggyCppError.setOutputType("executable");
+ exception.expect(GradleException.class);
+ exception.expectMessage("Both output name and output type must be specified");
+ ziggyCppError.getBuiltFile();
+ }
+
+ /**
+ * Tests that a missing output type produces the correct error.
+ */
+ @Test
+ public void testErrorNoOutputType() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ ziggyCppError.setCppFilePath(tempDir.getAbsolutePath() + "/src");
+ ziggyCppError.setBuildDir(buildDir);
+ ziggyCppError.setOutputName("dummy");
+ exception.expect(GradleException.class);
+ exception.expectMessage("Both output name and output type must be specified");
+ ziggyCppError.getBuiltFile();
+ }
+
+ /**
+ * Tests that missing both the output type and the built file name produces the correct error.
+ */
+ @Test
+ public void testErrorNoOutputTypeOrName() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ List cppPaths = new ArrayList<>();
+ ziggyCppError.setCppFilePath(tempDir.getAbsolutePath() + "/src");
+ ziggyCppError.setBuildDir(buildDir);
+ exception.expect(GradleException.class);
+ exception.expectMessage("Both output name and output type must be specified");
+ ziggyCppError.getBuiltFile();
+ }
+
+ /**
+ * Tests that a non-zero compiler return value produces the correct error.
+ * @throws ExecuteException
+ * @throws IOException
+ */
+ @Test
+ public void testCompilerError() throws ExecuteException, IOException {
+ ziggyCppObject.setOutputType("executable");
+ ziggyCppObject.setDefaultExecutor(defaultExecutor);
+ when(defaultExecutor.execute(any(CommandLine.class))).thenReturn(1);
+ exception.expect(GradleException.class);
+ exception.expectMessage("Compilation of file GetString.cpp failed");
+ ziggyCppObject.action();
+ }
+
+ /**
+ * Tests that a non-zero linker return value produces the correct error.
+ * @throws ExecuteException
+ * @throws IOException
+ */
+ @Test
+ public void testLinkerError() throws ExecuteException, IOException {
+ ziggyCppObject.setOutputType("executable");
+ ziggyCppObject.setDefaultExecutor(defaultExecutor);
+ String linkerCommand = ziggyCppObject.generateLinkCommand() + "GetString.o ZiggyCppMain.o ";
+ when(defaultExecutor.execute(ziggyCppObject.new CommandLineComparable(linkerCommand))).thenReturn(1);
+ exception.expect(GradleException.class);
+ exception.expectMessage("Link / library construction of dummy failed");
+ ziggyCppObject.action();
+ }
+
+ /**
+ * Test that an invalid OS produces the correct error.
+ */
+ @Test
+ public void testInvalidOsError() {
+ ZiggyCppPojo ziggyCppError = new ZiggyCppPojo();
+ ziggyCppError.setBuildDir(buildDir);
+ ziggyCppError.setOperatingSystem(OperatingSystem.WINDOWS);
+ ziggyCppError.setOutputName("dummy");
+ ziggyCppError.setOutputType("shared");
+ exception.expect(GradleException.class);
+ exception.expectMessage("ZiggyCpp class does not support OS "
+ + ziggyCppError.getOperatingSystem().getName());
+ ziggyCppError.getBuiltFile();
+ }
+
+//***************************************************************************************
+
+ // here begins assorted setup and helper methods
+
+ /**
+ * Creates four source files for use in the test. Two are C++ files in the tempDir/src
+ * directory, ZiggyCppMain.cpp and GetString.cpp. One is the header file ZiggyCppMain.h,
+ * also in tempDir/src. The final one is ZiggyCppLib.h, in tempdir/include.
+ * @throws FileNotFoundException
+ */
+ public void createSourceFiles() throws FileNotFoundException {
+
+ // content for ZiggyCppMain.cpp
+ String[] mainSourceContent =
+ {"#include \"ZiggyCppMain.h\"" ,
+ "#include ",
+ "#include \"ZiggyCppLib.h",
+ "",
+ "using namespace std;",
+ "",
+ "int main(int argc, const char* argv[]) {",
+ " string s = getString();",
+ " cout << s << endl;",
+ "}"
+ };
+
+ // content for the getString function
+ String[] getStringContent =
+ {"#include \"ZiggyCppMain.h\"",
+ "",
+ "using namespace std;",
+ "",
+ "string getString() {",
+ " return string(\"hello world!\");",
+ "}"
+ };
+
+ // content for the ZiggyCppMain.h header
+ String[] ziggyCppMainHeaderContent =
+ {"#ifndef ZIGGY_CPP",
+ "#define ZIGGY_CPP",
+ "#endif",
+ "",
+ "string getString();"
+ };
+
+ // content for the ZiggyCppLib.h header
+ String[] ziggyCppLibHeaderContent =
+ {"#ifndef ZIGGY_CPP_LIB",
+ "#define ZIGGY_CPP_LIB",
+ "#endif"
+ };
+
+ // create the source files first
+ PrintWriter mainSource = new PrintWriter(tempDir.getAbsolutePath() + "/src/ZiggyCppMain.cpp");
+ for (String line : mainSourceContent) {
+ mainSource.println(line);
+ }
+ mainSource.close();
+
+ PrintWriter getStringSource = new PrintWriter(tempDir.getAbsolutePath() + "/src/GetString.cpp");
+ for (String line : getStringContent) {
+ getStringSource.println(line);
+ }
+ getStringSource.close();
+
+ // put the main header in the src directory
+ PrintWriter h1 = new PrintWriter(tempDir.getAbsolutePath() + "/src/ZiggyCppMain.h");
+ for (String line : ziggyCppMainHeaderContent) {
+ h1.println(line);
+ }
+ h1.close();
+
+ // put the other header in the include directory
+ PrintWriter h2 = new PrintWriter(tempDir.getAbsolutePath() + "/include/ZiggyCppLib.h");
+ for (String line : ziggyCppLibHeaderContent) {
+ h2.println(line);
+ }
+ h2.close();
+
+ }
+
+ /**
+ * Add an additional source file in another directory to test accumulating files from multiple directories
+ * @throws FileNotFoundException
+ */
+ public void createAdditionalSource() throws FileNotFoundException {
+
+ // content for the getString function
+ String[] getStringContent =
+ {"#include \"ZiggyCppMain.h\"",
+ "",
+ "using namespace std;",
+ "",
+ "string getAnotherString() {",
+ " return string(\"hello again world!\");",
+ "}"
+ };
+
+ PrintWriter getStringSource = new PrintWriter(buildDir.getAbsolutePath() + "/src/cpp/GetAnotherString.cpp");
+ for (String line : getStringContent) {
+ getStringSource.println(line);
+ }
+ getStringSource.close();
+
+ }
+
+ /**
+ * Creates a ZiggyCpp object that is properly formed and has the following members populated:
+ * cppFilePath
+ * includeFilePaths
+ * compileOptions
+ * name
+ * Project (with a mocked Project object)
+ * @return properly-formed ZiggyCpp object
+ */
+ @SuppressWarnings("serial")
+ public ZiggyCppPojo createZiggyCppObject(File buildDir) {
+ ZiggyCppPojo ziggyCppObject = new ZiggyCppPojo();
+ ziggyCppObject.setCppFilePath(tempDir.getAbsolutePath() + "/src");
+ ziggyCppObject.setIncludeFilePaths(new ArrayList(){{
+ add(tempDir.getAbsolutePath() + "/src");
+ add(tempDir.getAbsolutePath() + "/include");
+ }});
+ ziggyCppObject.setCompileOptions(new ArrayList() {{
+ add("Wall");
+ add("fPic");
+ }});
+ ziggyCppObject.setReleaseOptimizations(new ArrayList() {{
+ add("O2");
+ add("DNDEBUG");
+ add("g");
+ }});
+ ziggyCppObject.setDebugOptimizations(new ArrayList() {{
+ add("Og");
+ add("g");
+ }});
+ ziggyCppObject.setOutputName("dummy");
+ ziggyCppObject.setBuildDir(buildDir);
+ ziggyCppObject.setCppCompiler("/dev/null/g++");
+ ziggyCppObject.setRootDir(rootDir);
+ return ziggyCppObject;
+ }
+
+ public void testStringListSettersAndGetters(String fieldName, String[] initialValues)
+ throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException {
+ String getter = "get" + fieldName;
+ String setter = "set" + fieldName;
+ int nOrigValues = initialValues.length;
+ Method getMethod = ZiggyCppPojo.class.getDeclaredMethod(getter, null);
+ Method setMethod = ZiggyCppPojo.class.getDeclaredMethod(setter, List.class);
+ List initialGetValues = (List) getMethod.invoke(ziggyCppObject, null);
+ assertEquals(nOrigValues, initialGetValues.size());
+ for (int i=0 ; i replacementValues = new ArrayList<>();
+ replacementValues.add("R1");
+ replacementValues.add("R2");
+ setMethod.invoke(ziggyCppObject, replacementValues);
+ List replacementGetValues = (List) getMethod.invoke(ziggyCppObject, null);
+ assertEquals(replacementValues.size(), replacementGetValues.size());
+ for (int i=0 ; i linkerOptions = new ArrayList<>();
+ linkerOptions.add("u whatevs");
+ ziggyCppObject.setLinkOptions(linkerOptions);
+ List libraryPathOptions = new ArrayList<>();
+ libraryPathOptions.add("/dummy1/lib");
+ libraryPathOptions.add("/dummy2/lib");
+ ziggyCppObject.setLibraryPaths(libraryPathOptions);
+ List libraryOptions = new ArrayList<>();
+ libraryOptions.add("hdf5");
+ libraryOptions.add("netcdf");
+ ziggyCppObject.setLibraries(libraryOptions);
+
+ }
+}
diff --git a/buildSrc/src/test/resources/gov/nasa/tess/buildutil/processed-jaxb.xsd b/buildSrc/src/test/resources/gov/nasa/tess/buildutil/processed-jaxb.xsd
new file mode 100644
index 0000000..6138e05
--- /dev/null
+++ b/buildSrc/src/test/resources/gov/nasa/tess/buildutil/processed-jaxb.xsd
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/buildSrc/src/test/resources/gov/nasa/tess/buildutil/raw-jaxb-generated.xsd b/buildSrc/src/test/resources/gov/nasa/tess/buildutil/raw-jaxb-generated.xsd
new file mode 100644
index 0000000..ee199fe
--- /dev/null
+++ b/buildSrc/src/test/resources/gov/nasa/tess/buildutil/raw-jaxb-generated.xsd
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/NPR-7150.2/Ziggy-NPR7150.2C.xlsx b/doc/NPR-7150.2/Ziggy-NPR7150.2C.xlsx
new file mode 100644
index 0000000..1b4bd6a
Binary files /dev/null and b/doc/NPR-7150.2/Ziggy-NPR7150.2C.xlsx differ
diff --git a/doc/build.gradle b/doc/build.gradle
new file mode 100644
index 0000000..0933172
--- /dev/null
+++ b/doc/build.gradle
@@ -0,0 +1,115 @@
+defaultTasks 'build'
+
+def getDate() {
+ def date = new Date()
+ def formattedDate = date.format('yyyyMMdd')
+ return formattedDate
+}
+
+task cleanestDryRun(type: Exec) {
+ description = "Removes pdf and .gradle directories (DRY RUN)."
+
+ outputs.upToDateWhen { false }
+
+ workingDir = rootDir
+ commandLine "sh", "-c", "git clean --force -x -d --dry-run"
+}
+
+task cleanest(type: Exec) {
+ description = "Removes pdf and .gradle directories."
+
+ outputs.upToDateWhen { false }
+
+ workingDir = rootDir
+ commandLine "sh", "-c", "git clean --force -x -d"
+}
+
+subprojects {
+ defaultTasks 'build'
+
+ task build() {
+ }
+
+ task makeDocId() {
+ description = "Generates a doc-id.sty file."
+
+ inputs.files fileTree(dir: projectDir, include: '**/*.tex', exclude: '**/build/**').files
+ outputs.file "$buildDir/doc-id.sty"
+
+ makeDocId.doFirst {
+ mkdir buildDir
+ }
+
+ doLast() {
+ if (!project.hasProperty('docId')) {
+ return
+ }
+
+ exec {
+ workingDir buildDir
+ commandLine "bash", "-c", "echo -E '\\newcommand{\\DOCID}{$docId}' > doc-id.sty"
+ }
+ }
+ }
+
+ task compileLaTex(dependsOn: makeDocId) {
+ description = "Compiles the .tex files into a .pdf file."
+
+ inputs.files fileTree(dir: projectDir, include: '**/*.tex', exclude: '**/build/**').files
+ outputs.files fileTree(dir: buildDir, include: '**/*.pdf').files
+
+ doFirst {
+ mkdir buildDir
+ }
+
+ doLast {
+ if (!project.hasProperty('texFileName')) {
+ return
+ }
+
+ // Execute twice to update references and a third time for BibTeX.
+ 3.times {
+ exec {
+ executable 'pdflatex'
+ workingDir project.workingDir
+ args '-output-directory=build'
+ args '-interaction=nonstopmode'
+ args '-halt-on-error'
+ args texFileName
+ }
+ }
+ }
+ }
+ build.dependsOn compileLaTex
+
+ task publish(dependsOn: build) {
+ description = "Publishes the .pdf file into the pdf directory."
+
+ inputs.dir buildDir
+ outputs.files fileTree(rootDir.getPath() + '/pdf').include('**/*-' + getDate() + '.pdf').files
+
+ doFirst() {
+ mkdir rootDir.getPath() + '/pdf/' + publishDir
+ }
+
+ doLast() {
+ if (!project.hasProperty('texFileName') || !project.hasProperty('publishDir') || !project.hasProperty('docId')) {
+ return
+ }
+
+ copy {
+ from(buildDir) {
+ rename '^(.*).pdf$', docId + '-$1-' + getDate() + '.pdf'
+ }
+ into rootDir.getPath() + '/pdf/' + publishDir
+ include '**/*.pdf'
+ }
+ }
+ }
+
+ task clean() {
+ doLast() {
+ delete buildDir
+ }
+ }
+}
diff --git a/doc/legal/NASA-Corporate-CLA.pdf b/doc/legal/NASA-Corporate-CLA.pdf
new file mode 100644
index 0000000..2e822fc
Binary files /dev/null and b/doc/legal/NASA-Corporate-CLA.pdf differ
diff --git a/doc/legal/NASA-Individual-CLA.pdf b/doc/legal/NASA-Individual-CLA.pdf
new file mode 100644
index 0000000..2a3adc6
Binary files /dev/null and b/doc/legal/NASA-Individual-CLA.pdf differ
diff --git a/doc/section-508/Ziggy-Section-508-Checklist.docx b/doc/section-508/Ziggy-Section-508-Checklist.docx
new file mode 100644
index 0000000..1f24f8d
Binary files /dev/null and b/doc/section-508/Ziggy-Section-508-Checklist.docx differ
diff --git a/doc/settings.gradle b/doc/settings.gradle
new file mode 100644
index 0000000..5dff80a
--- /dev/null
+++ b/doc/settings.gradle
@@ -0,0 +1,2 @@
+include 'hdf5-module-interface'
+include 'datastore'
diff --git a/doc/user-manual/advanced-topics.md b/doc/user-manual/advanced-topics.md
new file mode 100644
index 0000000..28085bb
--- /dev/null
+++ b/doc/user-manual/advanced-topics.md
@@ -0,0 +1,73 @@
+## Advanced Topics
+
+Here's where we get into some more fun stuff. Much of it is connected with the use of the console, so the advanced topics and Ziggy console section are combined.
+
+### High Performance Computing
+
+You don't really want to try to process 55 TB of flight data on your laptop, do you?
+
+[High Performance Computing Overview](select-hpc.md)
+
+[The Remote Execution Dialog Box](remote-dialog.md)
+
+[HPC Cost Estimation](hpc-cost.md)
+
+[Deleting Tasks](delete-tasks.md)
+
+### Data Receipt
+
+More about how Ziggy pulls information into the datastore.
+
+[Data Receipt Execution Flow](data-receipt.md)
+
+[Data Receipt Display](data-receipt-display.md)
+
+
diff --git a/doc/user-manual/alerts.md b/doc/user-manual/alerts.md
new file mode 100644
index 0000000..356356d
--- /dev/null
+++ b/doc/user-manual/alerts.md
@@ -0,0 +1,17 @@
+## Alerts Panel
+
+Ziggy uses alerts to tell the pipeline operator that something has happened that they ought to know about. Alerts are displayed on the `Alerts` status panel. It looks like this:
+
+![](images/monitoring-alerts.png)
+
+There are two flavors of alert that you're likely to see: warnings and errors. Warnings will turn the alerts stoplight yellow, errors turn it red. The alerts panel shows which task generated the alert, when it happened, and a hopefully-useful message. If there are no alerts, the stoplight will be green.
+
+Sadly, in this case it tells you pretty much what you already knew: task 12 blew up.
+
+### Acknowledging Alerts
+
+Once an alert arrives, the stoplight color will stay whatever color is appropriate for that alert (at least it will stay that color until another alert comes in). This may not be convenient: once you've dealt with whatever problem caused the alert, you'll want to start running again; at which point you'll want the alert stoplight to be green again so you can see if any new alerts come in, rather than staying yellow or red because of some issue that's already been fixed. To make the stoplight color turn back to green, use the `Ack` button. The alerts will still be shown on the table but the stoplight will return to green.
+
+### Clearing Alerts
+
+Alternately, once an alert is addressed you may want to get it completely out of the table of alerts. The `Clear` button will clear all alerts from the display and return the stoplight to green.
\ No newline at end of file
diff --git a/doc/user-manual/building-pipeline.md b/doc/user-manual/building-pipeline.md
new file mode 100644
index 0000000..e6a537f
--- /dev/null
+++ b/doc/user-manual/building-pipeline.md
@@ -0,0 +1,152 @@
+## Building Your Pipeline
+
+The question of whether your pipeline even needs a build system is one that only you can answer. If all the components of the pipeline are written in interpreted languages (Python, shell script, etc.), you might be able to get away without one!
+
+In this article, we review the "build system" for the sample pipeline, and in the process offer arguments why some kind of build system is a good idea in general.
+
+Before we move on, you should make sure that your environment variables `PIPELINE_CONFIG_PATH` and `ZIGGY_ROOT` are set correctly (as a reminder of what that means, take a look at the "Set up the Environment Variables" section of [the article on configuring a pipeline](configuring-pipeline.md)).
+
+### The Sample Pipeline Directory
+
+Just in case you haven't looked yet, here's what the sample pipeline directory should look like:
+
+```console
+sample-pipeline$ ls
+build-env.sh config data etc multi-data src
+sample-pipeline$
+```
+
+As we've discussed, the `src` directory is the various bits of source code for the pipeline, `etc` is the location of the pipeline properties file, `config` is the location of the XML files that define the pipeline. The `data` is the initial source of the data files that will be used by the sample pipeline.
+
+At the top you can see `build-env.sh`, which is the "build system" for the sample pipeline. In this case, the sample pipeline is so simple that none of the grown-up build systems were seen as needed or even desirable; a shell script would do what was needed.
+
+If you run the shell script from the command line (`./build-env.sh`), you should quickly see something that looks like this:
+
+```console
+sample-pipeline$ /bin/bash ./build-env.sh
+Collecting h5py
+ Using cached h5py-3.7.0-cp38-cp38-macosx_10_9_x86_64.whl (3.2 MB)
+Collecting Pillow
+ Using cached Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl (3.1 MB)
+Collecting numpy
+ Using cached numpy-1.23.4-cp38-cp38-macosx_10_9_x86_64.whl (18.1 MB)
+Installing collected packages: Pillow, numpy, h5py
+Successfully installed Pillow-9.2.0 h5py-3.7.0 numpy-1.23.4
+sample-pipeline$
+```
+
+
+
+Meanwhile, the directory now looks like this:
+
+```console
+sample-pipeline$ ls
+build build-env.sh config data etc multi-data src
+sample-pipeline$ ls build
+bin env pipeline-results
+sample-pipeline$
+```
+
+There's now a `build` directory that contains additional directories: `bin, data-receipt`, and `env`.
+
+#### The build-env.sh Shell Script
+
+Let's go through the build-env.sh script in pieces. The first piece is the familiar code chunk that sets up some shell variables with paths:
+
+```bash
+# Check for a SAMPLE_PIPELINE_PYTHON_ENV.
+if [ -n "$SAMPLE_PIPELINE_PYTHON_ENV" ]; then
+ if [ -z "$ZIGGY_HOME" ]; then
+ echo "SAMPLE_PIPELINE_PYTHON_ENV set but ZIGGY_HOME not set!"
+ exit 1
+ fi
+else
+ etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+ sample_home="$(dirname "$etc_dir")"
+ ZIGGY_HOME="$(dirname "$sample_home")"
+ SAMPLE_PIPELINE_PYTHON_ENV=$sample_home/build/env
+fi
+```
+
+Next:
+
+```bash
+# put the build directory next to the env directory in the directory tree
+BUILD_DIR="$(dirname "$SAMPLE_PIPELINE_PYTHON_ENV")"
+mkdir -p $SAMPLE_PIPELINE_PYTHON_ENV
+
+# Create and populate the data receipt directory from the sample data
+DATA_RECEIPT_DIR=$BUILD_DIR/data-receipt
+mkdir -p $DATA_RECEIPT_DIR
+cp $sample_home/data/* $DATA_RECEIPT_DIR
+```
+
+Here we create the `build` directory and its `env` and `data-receipt` directories. The contents of the data directory from the sample directory gets copied to `data-receipt`.
+
+```bash
+# build the bin directory in build
+BIN_DIR=$BUILD_DIR/bin
+mkdir -p $BIN_DIR
+BIN_SRC_DIR=$sample_home/src/main/sh
+
+# Copy the shell scripts from src to build. There's probably some good shell script
+# way to do this, but I'm too lazy.
+cp $BIN_SRC_DIR/permuter.sh $BIN_DIR/permuter
+cp $BIN_SRC_DIR/flip.sh $BIN_DIR/flip
+cp $BIN_SRC_DIR/averaging.sh $BIN_DIR/averaging
+chmod -R a+x $BIN_DIR
+```
+
+Here we construct `build/bin` and copy the shell scripts from `src/main/sh` to `build/bin`. In the process, we strip off the `.sh` suffixes. The shell script copies in `build/bin` now match what Ziggy expects to see.
+
+```bash
+python3 -m venv $SAMPLE_PIPELINE_PYTHON_ENV
+
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $SAMPLE_PIPELINE_PYTHON_ENV/bin/activate
+
+# Build the environment with the needed packages.
+pip3 install h5py Pillow numpy
+
+# Get the location of the environment's site packages directory
+SITE_PKGS=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Copy the pipeline major_tom package to the site-packages location.
+cp -r $ZIGGY_HOME/sample-pipeline/src/main/python/major_tom $SITE_PKGS
+
+# Copy the Ziggy components to the site-packages location.
+cp -r $ZIGGY_HOME/src/main/python/hdf5mi $SITE_PKGS
+cp -r $ZIGGY_HOME/src/main/python/zigutils $SITE_PKGS
+
+exit 0
+```
+
+Here at last we build the Python environment that will be used for the sample pipeline. The environment is built in `build/env`, and the packages `h5py`, `Pillow`, and `numpy` are installed in the environment. Finally, the sample pipeline source code and the Ziggy utility modules are copied to the `site-packages` directory of the environment, so that they will be on Python's search path when the environment is activated.
+
+### Okay, but Why?
+
+Setting up the `build` directory this way has a number of advantages. First and foremost, everything that Ziggy will eventually need to find is someplace in `build`. If your experience with computers is anything like mine, you know that 90% of what we spend our time doing is figuring out why various search paths aren't set correctly. Putting everything you need in one directory minimizes this issue.
+
+The second advantage is related: if you decide that you need to clean up and start over, you can simply delete the `build` directory. You don't need to go all over the place looking for directories and deleting them. Indeed, most grown up build systems will include a command that will automatically perform this deletion.
+
+### Why a Build System?
+
+If you look at build-env.sh, you'll see that it's relatively simple: just 80 lines, including comments and whitespace lines. Nonetheless, you wouldn't want to have to type all this in every time you want to generate the `build` directory! Having a build system -- any build system -- allows you to ensure that the build is performed reproducibly -- every build is the same as all the ones before. It allows you to implement changes to the build in a systematic way.
+
+### Which Build System?
+
+Good question! There are a lot of them out there.
+
+For simple systems, good old `make` is a solid choice. Support for `make` is nearly universal, and most computers ship with some version of it already loaded so you won't even need to install it yourself.
+
+For more complex systems, we're enamored of Gradle. The secret truth of build systems is that a build is actually a program: it describes the steps that need to be taken, their order, and provides conditionals of various kinds (like, "if the target is up-to-date, skip this step"). Gradle embraces this secret truth and runs with it, which makes it in many ways easier to use for bigger systems than `make`, with its frequently bizarre use of symbols and punctuation to represent relationships within the software.
+
+### Postscript
+
+Note that while going through all this exposition, we've also sneakily built the sample pipeline! This positions us for the next (exciting) step: [running the pipeline](running-pipeline.md)!
+
+Unfortunately, before you get there, we'll need to talk some about [relational databases](rdbms.md).
+
diff --git a/doc/user-manual/change-param-values.md b/doc/user-manual/change-param-values.md
new file mode 100644
index 0000000..ae5a6b4
--- /dev/null
+++ b/doc/user-manual/change-param-values.md
@@ -0,0 +1,28 @@
+## Changing Module Parameter Values
+
+To see how this works, go back to the `Configuration` tab and select the `Parameter Library` option from the menu on the left hand side. You'll see this:
+
+![](images/parameter-library.png)
+
+As you can see, all of the parameter sets defined in `pl-sample.xml` are represented here. What else does the table tell us?
+
+- The `Type` column is the name of the Java class that supports the module parameter set. For now you can ignore this.
+- The `Version` column shows the current version of the parameter set. They all show zero because none of the parameter sets has been modified since they were imported from `pl-sample.xml`.
+- The `Locked` column shows whether the current version of each parameter set is locked. What that means is this: before a version of a parameter set is used in the pipeline, it's unlocked, and the user can make changes to it; once the version has been used in processing, that version becomes locked, and any changes the user makes will create a new version (that is unused, hence unlocked). The versioning and locking features allow Ziggy to preserve a permanent record of the parameters used in each instance of each pipeline.
+
+Now: double-click the Algorithm Parameters row in the table. You'll get a new dialog box:
+
+
+
+The parameters that were defined as booleans in `pl-sample.xml` have check boxes you can check or uncheck. The other parameter types mostly behave the way you expect, but the array types offer some additional capabilities. If you click the `dummy array parameter` parameter, it will change thusly:
+
+
+
+If you click the "X", all the values will be deleted, which is rarely what you want. Instead click the other button. You'll get this window:
+
+
+
+This allows you to edit the array elements, remove them, add elements, etc., in a more GUI-natural way. Go ahead and change the second element (`idx` of 1) to 4. Click `ok`, then on the Edit Parameter Set dialog click `Save`. The `Version` for Algorithm Parameters will now be set to 1, and the `Locked` checkbox is unchecked.
+
+If you were to now run the sample pipeline, when you returned to the parameter library window, version 1 of `Algorithm Parameters` will show as locked.
+
diff --git a/doc/user-manual/configuring-pipeline.md b/doc/user-manual/configuring-pipeline.md
new file mode 100644
index 0000000..9fd2bd8
--- /dev/null
+++ b/doc/user-manual/configuring-pipeline.md
@@ -0,0 +1,241 @@
+## Configuring a Pipeline
+
+In this article, we'll walk through the process by which you can write your own pipeline and connect it to Ziggy. As we do so, we'll show how the sample pipeline addresses each of the steps, so you can see a concrete example. For this reason, it's probably worthwhile to have the sample-pipeline folder open as we go along (though we'll make use of screen shots, so it's not absolutely essential to have that open; just recommended).
+
+It also might be worthwhile to take open the [article on pipeline architecture](pipeline-architecture.md) in a separate window, as we'll be referring to it below.
+
+### Write the Algorithm Software
+
+At the heart of your pipeline are the algorithm packages that process the data and generate the results; on the architecture diagram, it's the big green "Algorithms" box on the bottom. On the one hand, we can't help you much with this -- only you know what you want your pipeline to do! On the other hand, Ziggy doesn't really put any particular requirements on how you do this. You can write what you want, the way you want it, in the language you want. At the moment, Ziggy has especially good support for C++, Java, MATLAB, and Python as algorithm languages, but really, it can be anything!
+
+In the sample pipeline, the algorithm code is in sample-pipeline/src/main/python/major_tom/major_tom.py; with a little luck, [this link](../../sample-pipeline/src/main/python/major_tom/major_tom.py) will open the file for you! There are 4 algorithm functions, each of which does some rather dopey image processing on PNG images: one of them permutes the color maps, one performs a left-right flip, one does an up-down flip, and one averages together a collection of PNG files. They aren't written particularly well, and I can't advocate for using them as an example of how to write Python code, but the point is that they don't do anything in particular to be usable in Ziggy.
+
+#### Pipeline Design
+
+That said, when you write your pipeline, there are a number of design issues that you must implicitly address:
+
+- What steps will the pipeline perform, and in what order?
+- What will be the file name conventions for the inputs and outputs of each step?
+- What additional information will each step need: instrument models, parameters, etc.
+
+The reason I bring this up is that these are the things that you'll need to teach to Ziggy so it knows how to run your pipeline for you. We'll get into that in the next few sections.
+
+### Write the Pipeline Configuration Files
+
+The issues described above are collectively the "pipeline configuration." This is represented on the architecture diagram by the green box in the upper left, "Pipeline Configuration (XML)." As advertised, Ziggy uses a set of XML files to define the pipeline steps, data types, etc. In the interest of this article not being longer than *Dune*, we're going to cover each of them in its own article:
+
+[Module Parameters](module-parameters.md)
+
+[Data File Types](data-file-types.md)
+
+[Pipeline Definition](pipeline-definition.md)
+
+### Write the "Glue" Code between Algorithms and Ziggy
+
+When writing the algorithm software, we asserted that there were no particular requirements on how the algorithms were written, which is true. However, it's also true that inevitably there has to be a certain amount of coding to the expectations of Ziggy, and this is what we mean when we talk about the "glue" code. "Glue" code is the code that is called directly by Ziggy and which then calls the algorithm code. For physicists and electrical engineers, you can think of this as providing an impedance match between Ziggy and the algorithms.
+
+Ziggy has really 3 requirements for the code it calls:
+
+1. The code has to be callable as a single executable with no arguments (i.e., it has to be something that could run at the command line).
+2. The code should be effectively unable to error out. What this means is that any non-trivial content should be in a try / catch or try / except block.
+3. The code has to return a value of 0 for successful execution, any other integer value for failure.
+
+There's also a "desirement:" in the event that the algorithm code fails, Ziggy would like a stack trace to be provided in a particular format.
+
+The good news here is that Ziggy will provide tools that make it easy to accomplish the items above. Also, Ziggy's "contract" with the algorithm code is as follows:
+
+1. The data files and instrument model files needed as input will be provided in the working directory that the algorithm uses (so you don't need to worry about search paths for these inputs).
+2. Results files can be written to the working directory (so you don't need to worry about sending them someplace special).
+3. Ziggy will provide a file in the working directory that specifies the names of all data files, the names of all model files, and the contents of all parameter sets needed by the algorithm. Thus it is not necessary for the "glue" code to hunt around in the working directory looking for files with particular name conventions; just open the file that defines the inputs and read its contents.
+
+#### "Glue" Code in the Sample Pipeline
+
+In the case of the sample pipeline, the "glue" code is actually written in 2 pieces: each algorithm module has 1 Python script plus 1 shell script that calls Python and gives it the name of the Python script to execute. We'll examine each of these in turn.
+
+##### Python-Side "Glue" Code
+
+For the purposes of this discussion we'll use the code that calls the `permute_colors` Python function: in the Python source directory (src/main/python/major_tom), it's [permuter.py](../../sample-pipeline/src/main/python/major_tom/permuter.py). This is the Python code that performs some housekeeping and then calls the permuter function in major_tom.py.
+
+The `permuter.py` module starts off with the usual collection of import statements:
+
+```Python
+from zigutils.stacktrace import ZiggyErrorWriter
+from hdf5mi.hdf5 import Hdf5ModuleInterface
+from zigutils.pidfile import write_pid_file
+from major_tom import permute_color
+```
+
+These are the Ziggy-specific imports (the other imports are standard Python packages and modules). The first, `ZiggyErrorWriter`, provides the class that writes the stack trace in the event of a failure; `Hdf5ModuleInterface` provides a specialized HDF5 module that reads the file with Ziggy's input information and writes the stack trace; `write_pid_file` writes to the working directory a hidden file that contains the process ID for the Python process that runs the algorithm, which is potentially useful later as a diagnostic; `permute_color` is the name of the algorithm function that `permuter.py` will run.
+
+The next block of code looks like this:
+
+```python
+# define an instance of the HDF5 read/write class as a global variable.
+hdf5_module_interface = Hdf5ModuleInterface()
+
+if __name__ == '__main__':
+ try:
+
+ # generate the process ID (PID) file in the working directory.
+ write_pid_file()
+
+ # Read inputs: note that the inputs contain the names of all the files
+ # that are to be used in this process, as well as model names and
+ # parameters. All files are in the working directory.
+ inputs = hdf5_module_interface.read_file("permuter-inputs-0.h5")
+ data_file = inputs.dataFilenames
+ parameters = inputs.moduleParameters.Algorithm_Parameters
+ models = inputs.modelFilenames
+
+```
+
+The main thing of interest here is that the HDF5 file with the inputs information is opened and read. The file name will always be the module name followed by `-inputs-0.h5`. The data object that's read from that file provides the data file names as a Python list in the `.filenames` field; all the module parameters in the `.moduleParameters` field; and the names of the models as a Python list in the `.modelFilenames` field. Note that, as described in [the article on parameter sets](module-parameters.md), the module parameter set named `Algorithm Parameters` in the parameters XML file is renamed to `Algorithm_Parameters` here.
+
+Next:
+
+```Python
+ # Handle the parameter values that can cause an error or cause
+ # execution to complete without generating output
+ dir_name = os.path.basename(os.getcwd())
+ if dir_name == "st-0":
+ throw_exception = parameters.throw_exception_subtask_0
+ else:
+ throw_exception = False
+
+ if dir_name == "st-1":
+ produce_output = parameters.produce_output_subtask_1
+ else:
+ produce_output = True
+```
+
+The main thing that's interesting here is that it shows how to access the individual parameters within a parameter set. You may well ask: What is this code actually doing with the parameters? Well, it's setting up to allow a demonstration of some of Ziggy's features later when we run the pipeline. For now, just focus on the fact that the parameters are subfields of the parameter struct, and that whitespace in the parameter names has been turned to underscores.
+
+```python
+ # Run the color permuter. The permute_color function will produce
+ # the output with the correct filename.
+ permute_color(data_file, throw_exception, produce_output)
+```
+
+Here the actual algorithm code is called. Thus we see that the Python-side "glue" has taken the information from Ziggy and reorganized it for use by the algorithm code.
+
+In this case the algorithm code doesn't return anything because it writes its outputs directly to the working directory. This is a choice, but not the only one: it would also be allowed for the algorithm code to return results, and for the "glue" code to perform some additional operations on them and write them to the working directory.
+
+Anyway, moving on to the last chunk of the Python-side "glue" code, we see this:
+
+Anyway, moving on to the last chunk of the Python-side "glue" code, we see this:
+
+```python
+ # Sleep for a user-specified interval. This is here just so the
+ # user can watch execution run on the pipeline console.
+ time.sleep(parameters.execution_pause_seconds)
+ exit(0)
+
+ except Exception:
+ ZiggyErrorWriter()
+ exit(1)
+```
+
+The first part of this block does something you definitely won't want to do in real life: it uses a module parameter to insert a pause in execution! We do this here for demonstration purposes only: the algorithms in the sample pipeline are so simple that they run instantaneously, but in real life that won't happen; so this slows down the execution so you can watch what's happening and get more of a feel for what real life will be like.
+
+Once execution completes successfully, the "glue" returns a value of 0. If an exception occurs at any point in all the foregoing, we jump to the `except` block: the stack trace is written and the "glue" returns 1.
+
+The foregoing is all the Python "glue" code needed by an algorithm. The equivalent code in MATLAB or Java or C++ are all about equally simple.
+
+###### Digression: Do We Really Need In-Language "Glue" Code?
+
+Not really. In principle, all of the "glue" code, above, could have been included in the algorithm function itself. So why do it this way?
+
+It's really a combination of convenience and division of labor. By separating the stuff that Ziggy needs into one file and the algorithm itself into another, the subject matter experts are free to develop the algorithm as they see fit and without need to worry about how it will fit into the pipeline. This also makes it easier for the algorithm code to run in a standalone or interactive way. This is especially helpful because the algorithm code is usually written, or at least prototyped, before the pipeline integration is performed. For example: in this case, all of the Python algorithm code in the sample pipeline was developed and debugged by running Python interactively; thus I provided an interface to each function that was optimal for interactive use (in this case, just send the name of the file you want to process as an argument). Once I was happy with the algorithm code, I wrote the Python "glue."
+
+This also means that the algorithm packages can still be run interactively, which is generally useful. If the algorithm functions also had all of the Ziggy argle-bargle in them, it wouldn't be possible to run the algorithm outside of the context of either Ziggy itself or else an environment that emulates Ziggy.
+
+Like I say, this isn't the only way to write a pipeline; but over time we've found that something like this has been the best way to do business.
+
+##### Shell Script "Glue" Code
+
+The shell script that provides the connection between permuter.py and Ziggy is in src/main/sh: [permuter.sh](../../sample-pipeline/src/main/sh/permuter.sh). The script can be seen as having 2 blocks of code. Here's the first:
+
+```bash
+# Check for a SAMPLE_PIPELINE_PYTHON_ENV.
+if [ -n "$SAMPLE_PIPELINE_PYTHON_ENV" ]; then
+ if [ -z "$ZIGGY_HOME" ]; then
+ echo "SAMPLE_PIPELINE_PYTHON_ENV set but ZIGGY_HOME not set!"
+ exit 1
+ fi
+else
+ etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+ sample_home="$(dirname "$etc_dir")"
+ ZIGGY_HOME="$(dirname "$sample_home")"
+ SAMPLE_PIPELINE_PYTHON_ENV=$sample_home/build/env
+fi
+```
+
+All this really does is define 2 variables: `ZIGGY_HOME`, the location of the Ziggy main directory; and `SAMPLE_PIPELINE_PYTHON_ENV`, the location of the Python environment for use in running the pipeline.
+
+The next block does all the actual work:
+
+```bash
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $SAMPLE_PIPELINE_PYTHON_ENV/bin/activate
+
+# Get the location of the environment's site packages directory
+SITE_PKGS=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Use the environment's Python to run the permuter Python script
+python3 $SITE_PKGS/major_tom/permuter.py
+
+# capture the Python exit code and pass it to the caller as the script's exit code
+exit $?
+```
+
+Again, really simple: activate the environment; find the location of the environment's site-packages directory; run `permuter.py` in the Python copy in the environment; return the exit code from `permuter.py` as the exit code from the shell script. The `trap` statement at the top is the way that shell scripts implement a kind of try-catch mentality: it says that when the script exits, no matter the reason or the condition of the exit, deactivate the Python environment on the way out the door.
+
+All of the above has focused on the permuter algorithm, but the same pattern of "glue" files and design patterns are used for the flip and averaging algorithms.
+
+All of the above has focused on the permuter algorithm, but the same pattern of "glue" files and design patterns are used for the flip and averaging algorithms.
+
+### Set up the Properties File
+
+As you can probably imagine, Ziggy actually uses a lot of configuration items: it needs to know numerous paths around your file system, which relational database application you want to use, how much heap space to provide to Ziggy, and on and on. All of this stuff is put into two locations for Ziggy: the pipeline properties file and the Ziggy properties file.
+
+For the sample pipeline, the pipeline properties file is [etc/sample.properties](../../sample-pipeline/etc/sample.properties). It uses a fairly standard name-value pair formalism, with capabilities for using property values or environment variables as elements of other properties.
+
+In real life, you would want the working properties file to be outside of the directories managed by the version control system. This allows you to modify the file without fear that you will accidentally push your changes back to the repository's origin! For our purposes, we've put together a pipeline properties file that you can use without modification, so feel free to just leave it where it is. We suggest that you start by copying the [pipeline.properties.EXAMPLE file](../../etc.pipeline.properties.EXAMPLE) to someplace outside of the Git-controlled directories, rename it, and modify it so that it suits your need.
+
+Meanwhile, The Ziggy properties file is [etc/ziggy.properties](../../etc/ziggy.properties), which is in the etc subdirectory of the main Ziggy directory. The properties here are things that you are unlikely to ever need to change, but which Ziggy needs.
+
+The properties file is a sufficiently important topic that it has its own separate article. See the article on [The Properties File](properties.md) for discussion of all the various properties in the pipeline properties file.
+
+### Set up the Environment Variables
+
+In a normal pipeline, you will need to set up only one environment variable: the variable `PIPELINE_CONFIG_PATH`, which has as its value the absolute path to the pipeline properties file. Ziggy can then use the pipeline properties file to get all its configuration parameters.
+
+For the sample pipeline, it was necessary to add a second environment variable: `ZIGGY_ROOT`, which is set to the absolute path to the top-level Ziggy directory. Why was this necessary?
+
+Under normal circumstances, the user would set the values of the path properties in the properties file based on their own arrangement of the file system, the location of the Ziggy directory, etc. All these things are known to the user, so the user can put all that path information into the pipeline properties file.
+
+In the case of the sample pipeline, we wanted to provide a properties file that would work for the end user, but we don't know anything about any end user's file system organization. We don't even know your username on your local system! So how do we make a properties file that will work for you without modification?
+
+Answer: the sample properties file sets all its paths relative to the top-level Ziggy directory. Of course, we here at Ziggy World Headquarters don't know that, either; it's unknown for the same reason that the user's username, file system arrangement, etc., are unknown. Thus, the user has to provide that information in the form of a second environment variable. And here we are.
+
+Anyway: set those properties now, before going any further.
+
+Anyway: set those properties now, before going any further.
+
+#### What About the Ziggy Properties File?
+
+The sample properties file contains a property that is the location of the Ziggy properties file. Thus there's no need to have a separate environment variable for that information. Like we said, to the extent possible we've put everything configuration related into the pipeline properties file.
+
+### And That's It
+
+Well, in fact we've covered quite a lot of material here! But once you've reached this point, you've covered everything that's needed to set up your own data analysis pipeline and connect it to Ziggy.
+
+That said, if you're paying attention you've probably noticed that this article ignored some issues, or at least posed some mysteries:
+
+- The shell script for the permuter module is `permuter.sh`, but the module name is `permuter`. Why isn't the module name `permuter.sh`?
+- You activate a Python environment, but -- where did that environment come from? What's in it?
+
+These questions will be discussed in the article on [Building a Pipeline](building-pipeline.md).
diff --git a/doc/user-manual/data-file-types.md b/doc/user-manual/data-file-types.md
new file mode 100644
index 0000000..bfa61b3
--- /dev/null
+++ b/doc/user-manual/data-file-types.md
@@ -0,0 +1,131 @@
+## Data File Types
+
+As the user, one of your jobs is to define, for Ziggy, the file naming patterns that are used for the inputs and outputs for each algorithm, and the file name patterns that are used for instrument models. The place for these definitions is in data file type XML files. These have names that start with "pt-" (for "Pipeline Data Type"); in the sample pipeline, the data file type definitions are in [config/pt-sample.xml](../../sample-pipeline/conf/pt-sample.xml).
+
+Note that when we talk about data file types, we're not talking about data file formats (like HDF5 or geoTIFF). Ziggy doesn't care about data file formats; use whatever you like, as long as the algorithm software can read and write that format.
+
+### The Datastore and the Task Directory
+
+Before we get too deeply into the data file type definitions, we need to have a brief discussion about two directories that Ziggy uses: the datastore, on the one hand, and the task directories, on the other.
+
+#### The Datastore
+
+"Datastore" here is just a $10 word for an organized directory tree where Ziggy keeps the permanent copies of its various kinds of data files. Files from the datastore are provided as inputs to the algorithm modules; when the modules produce results, those outputs are transferred back to the datastore.
+
+Who defines the organization of the datastore? You do! The organization is implicitly defined when you define the data file types that go into, and come out of, the datastore. This will become clear in a few paragraphs (at least I hope it's clear).
+
+#### The Task Directory
+
+Each processing activity has its own directory, known as the "task directory." The task directory is where the algorithm modules look to find the files they operate on, and it's where they put the files they produce as results. Unlike the datastore, these directories are transient; once processing is complete, you can feel free to delete them at some convenient time. In addition, there are some other uses that benefit from the task directory. First, troubleshooting. In the event that a processing activity fails, you have in one place all the inputs that the activity uses, so it's easy to inspect files, watch execution, etc. In fact, you can even copy the task directory to some other system (say, your laptop) if that is a more convenient place to do the troubleshooting! Second, and relatedly, the algorithm modules are allowed to write files to the task directory that aren't intended to be persisted in the datastore. This means that the task directory is a logical place to put files that are used for diagnostics or troubleshooting or some other purpose, but which you don't want to save for posterity in the datastore.
+
+#### And My Point Is?
+
+The key point is this: the datastore can be, and generally is, heirarchical; the task directory is flat. Files have to move back and forth between these two locations. The implications of this are twofold. First, **the filenames used in the datastore and the task directory generally can't be the same.** You can see why: because the datastore is heirarchical, two files that sit in different directories can have the same name. If those two files are both copied to the task directory, one of them will overwrite the other unless the names are changed when the files go to the task directory.
+
+Second, and relatedly, **the user has to provide Ziggy with some means of mapping the two filenames to one another.** Sorry about that; but the organization of the datastore is a great power, and with great power comes great responsibility.
+
+### Mission Data
+
+Okay, with all that throat-clearing out of the way, let's take a look at some sample data file type definitions.
+
+```xml
+
+
+
+```
+
+Each data file type has a name, and that name can have whitespace in it. That much makes sense.
+
+#### fileNameRegexForTaskDir
+
+This is how we define the file name that's used in the task directory. This is a [Java regular expression](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/regex/Pattern.html) (regex) that the file has to conform to. For `raw data`, for example, a name like `some-kinda-name-set-1-file-9.png` would conform to this regular expression, as would `another_NAME-set-4-file-3.png`, etc.
+
+#### fileNameWithSubstitutionsForDatastore
+
+Remember that the task directory is a flat directory, while the datastore can be heirarchical. This means that each part of the path to the file in the datastore has to be available somewhere in the task directory name, and vice-versa, so that the two can map to each other.
+
+In the `fileNameWithSubstitutionsForDatastore`, we accomplish this mapping. The way that this is done is that each "group" (one of the things in parentheses) is represented with $ followed by the group number. Groups are numbered from left to right in the file name regex, starting from 1 (group 0 is the entire expression). In raw data, we see a value of `$2/L0/$1-$3.png`. This means that group 2 is used as the name of the directory under the datastore root; `L0` is the name of the next directory down; and groups 1 and 3 are used to form the filename. Thus, `some-kinda-name-set-1-file-9.png` in the task directory would translate to `set-1/L0/some-kinda-name-file-9.png` in the datastore.
+
+Looking at the example XML code above, you can (hopefully) see what we said about how you would be organizing the datastore. From the example, we see that the directories immediately under the datastore root will be `set-0, set-1`, etc. Each of those directories will then have, under it, an `L0` directory and an `L1` directory. Each of those directories will then contain PNG files.
+
+Notice also that the filenames of `raw data` files and `permuted colors` files in the datastore can potentially be the same! This is allowed because the `fileNameWithSubstitutionsForDatastore` values show that the files are in different locations in the datastore, and the `fileNameRegexForTaskDir` values show that their names in the task directory will be different, even though their names in the datastore are the same.
+
+### Instrument Model Types
+
+Before we can get into this file type definition, we need to answer a question:
+
+#### What is an Instrument Model, Anyway?
+
+Instrument models are various kinds of information that are needed to process the data. These can be things like calibration constants; the location in space or on the ground that the instrument was looking at when the data was taken; the timestamp that goes with the data; etc.
+
+Generally, instrument models aren't the data that the instrument acquired (that's the mission data, see above). This is information that is acquired in some other way that describes the instrument properties. Like mission data, instrument models can use any file format that the algorithm modules can read.
+
+#### Instrument Model Type Definition
+
+Here's our sample instrument model type definition:
+
+ ` `
+
+As with the data file types, model types are identified by a string (in this case, the `type` attribute) that can contain whitespace, and provides a regular expression that can be used to determine whether any particular file is a model of the specified type. In this case, in a fit of no-imagination, the regex is simply a fixed name of `sample-model.txt`. Thus, any processing algorithm that needs the `dummy model` will expect to find a file named `sample-model.txt` in its task directory.
+
+#### Wait, is That It?
+
+Sadly, no. Let's talk about model names and how they fit into all of this.
+
+##### Datastore Model Names
+
+Ziggy permanently stores every model of every kind that is imported into it. This is necessary because someday you may need to figure out what model was used for a particular processing activity, but on the other hand it may be necessary to change the model as time passes -- either because the instrument itself changes with time, or because your knowledge of the instrument changes (hopefully it improves).
+
+But -- in the example above, the file name "regex" is a fixed string! This means that the only file name that Ziggy can possibly recognize as an instance of `dummy model` is `sample-model.txt`. So when I import a new version of `sample-model.txt` into the datastore, what happens? To answer that, let's take a look at the `dummy model` subdirectory of the `models` directory in the datastore:
+
+```console
+models$ ls "dummy\ model"
+2022-10-31.0001-sample-model.txt
+models$
+```
+
+(Yes, I broke my own strongly-worded caution against using whitespace in names, and in a place where it matters a lot -- a directory name! Consistency, hobgoblins, etc.)
+
+As you can see, the name of the model in the datastore isn't simply `sample-model.txt`. It's had the date of import prepended, along with a version number. By making these changes to the name, Ziggy can store as many versions of a model as it needs to, even if the versions all have the same name at the time of the import.
+
+##### Task Directory Model Names
+
+Ziggy also maintains a record of the name the model file had at the time of import. When the model is provided to the task directory so the algorithms can use it, this original name is restored. This way, the user never needs to worry about Ziggy's internal renaming conventions; the algorithms can use whatever naming conventions the mission uses for the model files, even if the mission reuses the same name over and over again.
+
+##### Which Version is Sent to the Algorithms?
+
+The most recent version of each model is the one provided to the algorithms at runtime. If there were 9 different models in `dummy model`, the one with version number `0009` would be the one that is copied to the task directories. If, some time later, a tenth version was imported, then all subsequent processing would use version `0010`.
+
+##### What Happens if the Actual Model Changes?
+
+Excellent question! Imagine that, at some point in time, one or more models change -- not your knowledge of them, the actual, physical properties of your instrument change. Obviously you need to put a new model into the system to represent the new properties of the instrument. But equally obviously, if you ever go back and reprocess data taken prior to the change, you need to use the model that was valid at that time. How does Ziggy handle that?
+
+Answer: Ziggy always, *always* provides the most recent version of the model file. If you go and reprocess, the new processing will get the latest model. In order to properly represent a model that changes with time, **the changes across time must be reflected in the most recent model file!** Also, and relatedly, **the algorithm code must be able to pull model for the correct era out of the model file!**
+
+In practice, that might mean that your model file contains multiple sets of information, each of which has a datestamp; the algorithm would then go through the file contents to find the set of information with the correct datestamp, and use it. Or, it might mean that the "model" is values measured at discrete times that need to be interpolated by the algorithm. How the time-varying information is provided in the model file is up to you, but if you want to have a model that does change in time, this is how you have to do it.
+
+##### Model Names with Version Information
+
+The above example is kind of unrealistic because in real life, a mission that provides models that get updated will want to put version information into the file name; if for no other reason than so that when there's a problem and we need to talk about a particular model version, we can refer to the one we're concerned about without any confusion ("Is there a problem with sample model?" "Uh, which version of sample model?" "Dunno, it's just called sample model."). Thus, the file name might contain a timestamp, a version number, or both.
+
+If the model name already has this information, it would be silly for Ziggy to prepend its own versioning; it should use whatever the mission provides. Fortunately, this capability is provided:
+
+```xml
+
+```
+
+In this case, the XML attribute `versionNumberGroup` tells Ziggy which regex group it should use as the version number, and the attribute `timestampGroup` tells it which to use as the file's timestamp. When Ziggy stores this model in the `versioned-model` directory, it won't rename the file; it will keep the original file name, because the original name already has a timestamp and a version number.
+
+In general, the user can include in the filename a version number; a timestamp; or both; or neither. Whatever the user leaves out, Ziggy will add to the filename for internal storage, and then remove again when providing the file to the algorithms.
+
+##### Models Never Get Overwritten in the Datastore
+
+One thing about supplying timestamp and version information in the filename is that it gives some additional protection against accidents. **Specifically: Ziggy will never import a model that has the same timestamp and version number as one already in the datastore.** Thus, you can never accidentally overwrite an existing model with a new one that's been accidentally given the same timestamp and version information.
+
+For models that don't provide that information in the filename, there's no protection against such an accident because there can't be any such protection. If you accidentally re-import an old version of `sample-model.txt`, Ziggy will assume it's a new version and store it with a new timestamp and version number. When Ziggy goes to process data, this version will be provided to the algorithms.
diff --git a/doc/user-manual/data-receipt-display.md b/doc/user-manual/data-receipt-display.md
new file mode 100644
index 0000000..e7ea57f
--- /dev/null
+++ b/doc/user-manual/data-receipt-display.md
@@ -0,0 +1,11 @@
+## Data Receipt Display
+
+The console has the ability to display data receipt activities. From the `Configuration` tab, expand the `Data Receipt` folder and select `Available Datasets`. You'll see something like this:
+
+![](images/data-receipt-display.png)
+
+Double-clicking a row in the table brings up a display of all the files in the dataset:
+
+![](images/data-receipt-list.png)
+
+Note that the file names are the datastore names.
\ No newline at end of file
diff --git a/doc/user-manual/data-receipt.md b/doc/user-manual/data-receipt.md
new file mode 100644
index 0000000..9ae1be2
--- /dev/null
+++ b/doc/user-manual/data-receipt.md
@@ -0,0 +1,157 @@
+## Data Receipt Execution Flow
+
+Remember data receipt? Here's where we get into how it works. You'll want to know this when you're setting up data transfers in your own mission.
+
+### Data Files and Manifest
+
+The sample pipeline's data receipt directory uses a copy of the files from the `data` subdirectory in the `sample-pipeline` main directory. Let's take a look at that directory now:
+
+```console
+sample-pipeline$ ls data
+nasa_logo-set-1-file-0.png
+nasa_logo-set-1-file-3.png
+nasa_logo-set-2-file-2.png
+sample-pipeline-manifest.xml
+nasa_logo-set-1-file-1.png
+nasa_logo-set-2-file-0.png
+nasa_logo-set-2-file-3.png
+nasa_logo-set-1-file-2.png
+nasa_logo-set-2-file-1.png
+sample-model.txt
+sample-pipeline$
+```
+
+Most of these files are obviously the files that get imported. But what about the manifest? Here's the contents of the manifest:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Some parts of this are obvious: the number of files in the delivery, the fact that every file has an entry in the manifest, every file's size is listed in the manifest.
+
+The `datasetId` is a unique identifier for a data delivery. This serves two purposes:
+
+1. It prevents you from re-importing the same files multiple times, as the `datasetId` from each successful import is saved and new imports are checked against them (exception: `datasetId` 0 can be reused).
+2. When there's a problem with the delivery and you need to work the issue with whoever sent it to you, it lets you refer to exactly which one failed: "Yeah, uh, looks like dataset 12345 won't import. Got a minute?"
+
+The `checksumType` is the name of the algorithm that's used to generate a checksum for each file in the manifest. In this example we're using SHA1, which is a reasonable balance between calculation speed, checksum size, and checksum quality (anyway, we're not using these SHA1 hashes for secure communication, we're just using them to make sure the files didn't get corrupted).
+
+Each file has a `checksum` that's computed using the specified `checksumType`.
+
+### The Data Receipt Directory
+
+Data receipt needs to have a directory that's used as the source for files that get pulled into the datastore. There's a [property in the properties file](properties.md) that specifies this, namely `data.receipt.dir`. Ziggy allows this directory to be used in either of two ways.
+
+#### Files in the Data Receipt Directory
+
+Option 1 is for all the files, and the manifest, to be in the data receipt directory. In this case, the data receipt pipeline node will produce 1 task.
+
+#### Files in Data Receipt Subdirectories
+
+Option 2 is that there are no data files or manifests in the top-level data receipt directory. Instead, there are subdirectories within data receipt, each of which contains files for import and a manifest. In this case, data receipt will create a pipeline task per subdirectory.
+
+### What Data Receipt Does
+
+Here's the steps data receipt takes, in order.
+
+#### Validate the Manifest
+
+In this step, Ziggy checks that every file that's present in the manifest is also present in the directory, and that the size and checksum of every file is correct. Ziggy produces an acknowledgement file that lists each file with its transfer status (was the file there?) and its validation status (were the size and checksum correct?), plus an overall status for the transfer (did any file fail either of its validations?).
+
+#### Look for Files Not Listed in the Manifest
+
+The step above ensures that every file in the manifest was transferred, but it doesn't rule out that there were extra files transferred that aren't in the manifest. Ziggy now checks to make sure that there aren't any such extra files, and throws an exception if any are found. The idea here is that Ziggy can't tell whether an extra file is supposed to be imported or not, and if it is supposed to be imported there's no size or checksum information to validate it with. So to be on the safe side, better to stop and ask for help.
+
+#### Do the Imports
+
+At this point Ziggy loops through the directory and imports all the files into the datastore. As each file is imported into the datastore it's removed from the data receipt directory.
+
+#### Clean Up the Data Receipt Directory
+
+All manifest and acknowledgement files are transferred to the `manifests` sub-directory of the `logs` directory.
+
+Empty subdirectories of the data receipt directory are removed. If the data receipt directory has any remaining content other than the .manifests directory, an exception is thrown. An exception at this point due to non-empty directories means that files that were supposed to be imported weren't.
+
+### The Acknowledgement XML File
+
+In the interest of completeness, here's the content of the acknowledgement file for the sample pipeline data delivery:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Note that the manifest file must end with "`-manifest.xml`", and the acknowledgement file will end in "`-manifest-ack.xml`", with the filename prior to these suffixes being the same for the two files.
+
+### Systems that Treat Directories as Data Files
+
+There may be circumstances in which it's convenient to put several files into a directory, and then to use a collection of directories of that form as "data files" for the purposes of data processing. For example, consider a system where there's a data file with an image, and then several files that are used to background-subtract the data file. Rather than storing each of those files separately, you might put the image file and its background files into a directory; import that directory, as a whole, into the datastore; then supply that directory, as a whole, as an input for a subtask.
+
+In that case, the manifest still needs to have an entry for each regular file, but in this case the name of the file includes the directory it sits in. Here's what that looks like in this example:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Now the only remaining issue is how to tell Ziggy to import the files in such a way that each of the `data-#####` directories is imported and stored as a unit. To understand how that's accomplished, let's look back at the data receipt node in `pd-sample.xml`:
+
+```xml
+
+
+
+
+```
+
+Meanwhile, the definition of the raw data type is in `pt-sample.xml`:
+
+```xml
+
+```
+
+Taken together, these two XML snippets tell us that data receipt's import is going to import files that match the file name convention for the `raw data` file type. We can do the same thing when the "file" to import is actually a directory. If you define a data file type that has `fileNameRegexForTaskDir` set to `data-[0-9]{5}`, Ziggy will import directory `data-00001` and all of its contents as a unit and store that unit in the datastore, and so on.
+
+Note that the manifest ignores the fact that import of data is going to treat the `data-#####` directories as the "files" it imports, and the importer ignores that the manifest validates the individual files even if they are in these subdirectories.
+
+### Generating Manifests
+
+Ziggy also comes with a utility to generate manifests from the contents of a directory. Use `runjava generate-manifest`. This utility takes 3 command-line arguments:
+
+1. Manifest name, required.
+2. Dataset ID, required.
+3. Path to directory with files to be put into the manifest, optional (default: working directory).
+
diff --git a/doc/user-manual/datastore-task-dir.md b/doc/user-manual/datastore-task-dir.md
new file mode 100644
index 0000000..7d12d9b
--- /dev/null
+++ b/doc/user-manual/datastore-task-dir.md
@@ -0,0 +1,190 @@
+## The Datastore and the Task Directory
+
+The datastore is Ziggy's organized, permanent file storage system. The task directory is temporary file storage used by processing algorithms. Let's take a look at these now.
+
+### The Datastore
+
+Before we can look at the datastore, we need to find it! Fortunately, we can refer to the [properties file](properties.md). Sure enough, we see this:
+
+```
+pipeline.root.dir = ${ziggy.root}/sample-pipeline
+pipeline.home.dir = ${pipeline.root.dir}/build
+pipeline.results.dir = ${pipeline.home.dir}/pipeline-results
+datastore.root.dir = ${pipeline.results.dir}/datastore
+```
+
+Well, you don't see all of those lines laid out as conveniently as the above, but trust me, they're all there. Anyway, what this is telling us is that Ziggy's data directories are in `build/pipeline-results/datastore`. Looking at that location we see this:
+
+```console
+datastore$ tree
+├── models
+│ └── dummy model
+│ └── 2022-10-31.0001-sample-model.txt
+├── set-1
+│ ├── L0
+│ │ ├── nasa_logo-file-0.png
+│ │ ├── nasa_logo-file-1.png
+│ │ ├── nasa_logo-file-2.png
+│ │ └── nasa_logo-file-3.png
+│ ├── L1
+│ │ ├── nasa_logo-file-0.png
+│ │ ├── nasa_logo-file-1.png
+│ │ ├── nasa_logo-file-2.png
+│ │ └── nasa_logo-file-3.png
+│ ├── L2A
+│ │ ├── nasa_logo-file-0.png
+│ │ ├── nasa_logo-file-1.png
+│ │ ├── nasa_logo-file-2.png
+│ │ └── nasa_logo-file-3.png
+│ ├── L2B
+│ │ ├── nasa_logo-file-0.png
+│ │ ├── nasa_logo-file-1.png
+│ │ ├── nasa_logo-file-2.png
+│ │ └── nasa_logo-file-3.png
+│ └── L3
+│ └── averaged-image.png
+└── set-2
+ datastore$
+```
+
+Summarizing what we see:
+
+- a `models` directory, with a `dummy model` subdirectory and within that a sample model.
+- A `set-1` directory and a `set-2` directory. The `set-2` directory layout mirrors the layout of `set-1`; take a look if you don't believe me, I didn't bother to expand set-2 in the interest of not taking up too much space.
+- Within `set-1` we see a directory `L0` with some PNG files in it, a directory `L1` with some PNG files, and then `L2A`, `L2B`, and `L3` directories which (again, trust me or look for yourself) contain additional PNG files.
+
+Where did all this come from? Let's take a look again at part of the `pt-sample.xml` file:
+
+```xml
+
+
+
+```
+
+If you don't remember how data file type definitions worked, feel free to [go to the article on Data File Types](data-file-types.md) for a quick refresher course. In any event, you can probably now see what we meant when we said that the data file type definitions implicitly define the structure of the datastore. The `set-1/L0` and `set-2/L0` directories come from the `fileNameWithSubstitutionsForRegex` value for raw data; similarly the permuted colors data type defines the `set-1/L1` and `set-2/L2` directories.
+
+#### Model Names in the Datastore
+
+If you look at the figure above, you've probably noticed that the name of the dummy model has been mangled in some peculiar fashion: instead of `sample-model.txt`, the name is `2002-09-19.0001-sample-model.txt`. What's up with that?
+
+Well -- models are different from mission data in that it's sometimes necessary to update models; but it's also necessary to keep every copy of every model, because we need to be able to work out the full provenance of all Ziggy's data products, which includes knowing which models were used for every processing activity. But the name "regex" for the dummy model is just `sample-model.txt`. That means that every version of the model has to have the same name, which means that ordinarily a new model file would overwrite an old one. And that's not acceptable.
+
+So: when a model is imported, its "datastore name" includes the import date and a version number (starting at 1) that get prepended to the file name. Thus the name of the file we see in the datastore. Version numbers increase monotonically and are never re-used (thus are unique), but multiple models of a given type can have the same timestamp.
+
+If you think you're going to have more than 9,999 versions of a model, let us know and we'll change the format to accommodate you.
+
+Note that when the model gets copied to a task directory, any Ziggy-supplied version number or timestamp will be removed and the filename seen by the algorithm will match the original name of the model file.
+
+#### Supplying Your Own Version Information for Models
+
+Of course, it's also possible (indeed, likely) that any actual flight mission will include version and datestamp information in the name of its model files. You can imagine a model name regex that's something like "`calibration-constants-([0-9]{8}T[0-9]{6})-v([0-9]+).xml`", where the first group in the regex is a datestamp in ISO 8601 format (YYYYMMDDThhmmss), and the second group is a version number. In this case, you might want Ziggy to use the datestamp and version number from the filename and not append its own (if for no other reason than having 2 datestamps and 2 version numbers in the datastore filename would look dumb). You can make this happen by specifying in the XML the group number for the timestamp and the group number for the version:
+
+```xml
+
+```
+
+You can use this mechanism to specify that filenames have version numbers in them; or timestamps, or both. If the filename has only one of the two, Ziggy will prepend its version of the other to the filename when storing in the datastore.
+
+### The Task Directory
+
+When Ziggy runs algorithm code, it doesn't the algorithm direct access to files in the datastore. Instead, each pipeline task gets its own directory, known as the "task directory" (clever!).
+
+To find the task directory, look first to the pipeline results location in the properties file:
+
+```
+pipeline.results.dir=${pipeline.home.dir}/pipeline-results
+```
+
+The pipeline-results directory contains a number of subdirectories. First, let's look at task-data:
+
+```
+task-data$ ls
+1-2-permuter 1-4-flip 1-6-averaging 2-10-flip 2-8-permuter
+1-3-permuter 1-5-flip 1-7-averaging 2-11-flip 2-9-permuter
+task-data$
+```
+
+Every pipeline task has its own directory. The name of a task's directory is the instance number, the task number, and the module name, separated by hyphens. If we drill down into `1-2-permuter`, we see this:
+
+```console
+1-2-permuter$ ls -R
+ARRIVE_PFE.1667003280019 QUEUED_PBS.1667003279236 st-1
+PBS_JOB_FINISH.1667003320029 permuter-inputs.h5 st-2
+PBS_JOB_START.1667003280021 st-0 st-3
+
+1-2-permuter/st-0:
+SUB_TASK_FINISH.1667003287519 nasa_logo-set-2-file-0-perm.png permuter-inputs-0.h5 sample-model.txt
+SUB_TASK_START.1667003280036 nasa_logo-set-2-file-0.png permuter-stdout-0.log
+
+1-2-permuter/st-1:
+SUB_TASK_FINISH.1667003294982 nasa_logo-set-2-file-1-perm.png permuter-inputs-0.h5 sample-model.txt
+SUB_TASK_START.1667003287523 nasa_logo-set-2-file-1.png permuter-stdout-0.log
+
+1-2-permuter/st-2:
+SUB_TASK_FINISH.1667003302619 nasa_logo-set-2-file-2-perm.png permuter-inputs-0.h5 sample-model.txt
+SUB_TASK_START.1667003294987 nasa_logo-set-2-file-2.png permuter-stdout-0.log
+
+1-2-permuter/st-3:
+SUB_TASK_FINISH.1667003310303 nasa_logo-set-2-file-3-perm.png permuter-inputs-0.h5 sample-model.txt
+SUB_TASK_START.1667003302623 nasa_logo-set-2-file-3.png permuter-stdout-0.log
+1-2-permuter$
+```
+
+At the top level there's some stuff we're not going to talk about now. What's interesting is the contents of the subtask directory, st-0:
+
+- The sample model is present with its original (non-datastore) name, `sample-model.txt`.
+- The inputs file for this subtask is present, also with its original (non-datastore) name, `nasa-logo-set-2-file-0.png`.
+- The outputs file for this subtask is present: `nasa-logo-set-2-file-0-perm.png`.
+- The HDF5 file that contains filenames is present: `permuter-inputs-0.h5`.
+- There's a file that contains all of the standard output (i.e., printing) from the algorithm: `permuter-stdout-0.log`.
+- There are a couple of files that show the Linux time that the subtask started and completed processing.
+
+### The Moral of this Story
+
+So what's the takeaway from all this? Well, there's actually a couple:
+
+- Ziggy maintains separate directories for its permanent storage in the datastore and temporary storage for algorithm use in the task directory.
+- The task directory, in turn, contains one directory for each subtask.
+- The subtask directory contains all of the content that the subtask needs to run. This is convenient if troubleshooting is needed: you can copy a subtask directory to a different computer to be worked on, rather than being forced to work on it on the production file system used by Ziggy.
+- There's some name mangling between the datastore and the task directory.
+- You can put anything you want into the subtask or task directory; Ziggy only pulls back the results it's been told to pull back. This means that, if you want to dump a lot of diagnostic information into each subtask directory, which you only use if something goes wrong in that subtask, feel free; Ziggy won't mind.
+
+### Postscript: Copies vs. Symbolic Links
+
+If you look closely at the figure that shows the task directory, you'll notice something curious: the input and output "files" aren't really files. They're symbolic links. Specifically, they're symbolic links to files in the datastore. Looking at an example:
+
+```console
+st-0$ ls -l
+total 64
+-rw-r--r-- 1 0 Oct 31 16:01 SUB_TASK_FINISH.1667257285445
+-rw-r--r-- 1 0 Oct 31 16:01 SUB_TASK_START.1667257269376
+lrwxr-xr-x 1 104 Oct 31 16:01 nasa_logo-set-2-file-0-perm.png -> ziggy/sample-pipeline/build/pipeline-results/datastore/set-2/L1/nasa_logo-file-0.png
+lrwxr-xr-x 1 104 Oct 31 16:01 nasa_logo-set-2-file-0.png -> ziggy/sample-pipeline/build/pipeline-results/datastore/set-2/L0/nasa_logo-file-0.png
+-rw-r--r-- 1 25556 Oct 31 16:01 permuter-inputs-0.h5
+-rw-r--r-- 1 174 Oct 31 16:01 permuter-stdout-0.log
+lrwxr-xr-x 1 126 Oct 31 16:01 sample-model.txt -> ziggy/sample-pipeline/build/pipeline-results/datastore/models/dummy model/2022-10-31.0001-sample-model.txt
+st-0$
+```
+
+Ziggy allows the user to select whether to use actual copies of the files or symbolic links. This is configured in -- yeah, you got it -- the properties file:
+
+```
+moduleExe.useSymlinks = true
+```
+
+The way this works is obvious for the input files: Ziggy puts a symlink in the working directory, and that's all there is to it. For the outputs file, what happens is that the algorithm produces an actual file of results; when Ziggy goes to store the outputs file, it moves it to the datastore and replaces it in the working directory with a symlink. This is a lot of words to say that you can turn this feature on or off at will and your code doesn't need to do anything different either way.
+
+The advantages of the symlinks are fairly obvious:
+
+- Symbolic links take up approximately zero space on the file system. If you use symbolic links you avoid having multiple copies of every file around (one in the datastore, one in the subtask directory). For large data volumes, this can be valuable.
+- Similarly, symbolic links take approximately zero time to instantiate. Copies take actual finite time. Again, for large data volumes, it can be a lot better to use symlinks than copies in terms of how much time your processing needs.
+
+There are also situations in which the symlinks may not be a good idea:
+
+- It may be the case that you're using one computer to run the worker and database, and a different one to run the algorithms. In this situation, the datastore can be on a file system that's mounted on the worker machine but not the compute machine, in which case the symlink solution won't work (the compute node can't see the datastore, so it can't follow the link).
diff --git a/doc/user-manual/delete-tasks.md b/doc/user-manual/delete-tasks.md
new file mode 100644
index 0000000..d870088
--- /dev/null
+++ b/doc/user-manual/delete-tasks.md
@@ -0,0 +1,25 @@
+## Deleting Tasks
+
+Sometimes it's necessary to stop the execution of tasks after they start running. Tasks that are running as jobs under control of a batch system at an HPC facility will provide command line tools for this, but they're a hassle to use when you're trying to delete a large number of jobs. Trying to delete tasks running locally is likewise hassle-tastic.
+
+Fortunately, Ziggy will let you do this from the console.
+
+### Delete all Jobs for a Task
+
+To delete all jobs for a task, go to the tasks table on the Instances panel, right click the task, and select `Delete tasks` from the pop-up menu:
+
+![delete-task-menu-item](images/delete-task-menu-item.png)
+
+You'll be prompted to confirm that you want to delete the task. When you do that, you'll see something like this:
+
+![delete-in-progress](images/delete-in-progress.png)
+
+The state of the task will be immediately moved to `ERROR`, P-state `ALGORITHM_COMPLETE`. The instance will go to state `ERRORS_RUNNING` because the other task is still running; once it completes, the instance will go to `ERRORS_STALLED`. Meanwhile, the alert looks like this:
+
+![delete-alert](images/delete-alert.png)
+
+As expected, it notifies you that the task stopped due to deletion and not due to an error of some kind.
+
+### Delete all Tasks for an Instance
+
+This is the same idea, except it's the pop-up menu for the instance table, and you select `Delete all tasks`.
\ No newline at end of file
diff --git a/doc/user-manual/display-logs.md b/doc/user-manual/display-logs.md
new file mode 100644
index 0000000..d226c1e
--- /dev/null
+++ b/doc/user-manual/display-logs.md
@@ -0,0 +1,23 @@
+## Log Files Display
+
+Ziggy provides a mechanism for viewing task logs that is more convenient than going to the `logs` directory and hunting around.
+
+To use this feature, go to the Instance tab under the Operations tab. Select the task of interest and right-click to bring up the tasks menu:
+
+
+
+Select `List task logs`. You'll get this dialog box:
+
+![](images/logs-list.png)
+
+By default the logs are ordered by name, which means that they're also ordered by time, from earliest to latest. If you double-click on one of the rows in the table, the log file in question will be opened in a new window:
+
+![](images/task-log-display.png)
+
+The log will always be opened with the view positioned at the end of the log, since that's most often where you can find messages that inform you about the problems. In this case, that's not true, so you can use the `To Top` button to jump to the start of the log, or simply scroll around until you find what you're looking for:
+
+![](images/task-log-showing-problem.png)
+
+Here you can see the stack trace produced by the Python algorithm when it deliberately threw an exception, and the Java stack trace that was generated when Ziggy detected that the algorithm had thrown an exception. As it happens, the log shows exactly what the problem is: the user set the parameter that tells subtask 0 to fail, and subtask 0 duly failed.
+
+Note that this is the same content we saw in the subtask algorithm log in the subtask directory (if you don't remember what I'm talking about, it's near the bottom of [the Log Files article](log-files.md)). The difference is that the file in the subtask directory only has the output from one subtask, while the task log has all the logging from all the subtasks plus additional logging from the Ziggy components that run and manage the algorithm execution.
diff --git a/doc/user-manual/downloading-and-building-ziggy.md b/doc/user-manual/downloading-and-building-ziggy.md
new file mode 100644
index 0000000..3f41721
--- /dev/null
+++ b/doc/user-manual/downloading-and-building-ziggy.md
@@ -0,0 +1,105 @@
+## Downloading and Building Ziggy
+
+Before you start, you should check out the [system requirements](system-requirements.md) article. This will ensure that you have the necessary hardware and software to follow the steps in this article.
+
+### Downloading Ziggy
+
+Ziggy's source code is stored on GitHub, which you probably know already since you're reading this document, which is also stored on GitHub along with Ziggy. A discussion of GitHub is way beyond the scope of this document, but the things you need to know to download Ziggy are a really small subset of that:
+
+TODO BW put in what we need to say about GitHub.
+
+Once you've done that, you should see something like this in your Ziggy folder:
+
+```console
+ziggy$ ls
+LICENSE.pdf doc gradlew script-plugins
+README.md etc ide settings.gradle
+build.gradle gradle licenses src
+buildSrc gradle.properties sample-pipeline test
+ziggy$
+```
+
+Let's go through these items:
+
+- The files `build.gradle`, `settings.gradle`, `gradle.properties`, and `gradlew` are used by our build system. Hopefully you won't need to know anything more about them than that.
+- Likewise, directories `gradle` and `script-plugins` are used in the build.
+- The `buildSrc` directory contains some Java and Groovy classes that are part of Ziggy but are used by the build. The idea here is that Gradle allows users to extend it by defining new kinds of build tasks; those new kinds of build tasks are implemented as Java or Groovy classes and by convention are put into a "buildSrc" folder. This is something else you probably won't need to worry about; certainly not any time soon.
+- The `doc` directory contains this user manual, plus a bunch of other, more technical documentation.
+- The `etc` directory contains files that are used as configuration inputs to various programs. This is things like: the file that tells the logger how to format text lines, and so on. Two of these are going to be particularly useful and important to you: the ziggy.properties file, and the pipeline.properties.EXAMPLE file. These files provide all of the configuration that Ziggy needs to locate executables, working directories, data storage, and etc. We'll go into a lot of detail on this at the appropriate time.
+- The `ide` directory contains auxiliary files that are useful if you want to develop Ziggy in the Eclipse IDE.
+- The `licenses` directory contains information about both the Ziggy license and the licenses of third-party applications and libraries that Ziggy uses.
+- The `src` directory contains the directory tree of Ziggy source code, both main classes and test classes.
+- The `test` directory contains test data for Ziggy's unit tests.
+- The `sample-pipeline` directory contains the source and such for the sample pipeline.
+
+### Building Ziggy
+
+Before you build Ziggy, you'll need to set up 4 environment variables:
+
+- The `JAVA_HOME` environment variable should contain the location of the Java Development Kit (JDK) you want to use for Java compilation.
+- The `CC` environment variable should contain the location of the C compiler you want to use for C compilation.
+- The `CXX` environment variable should contain the location of the C++ compiler you want to use for C++ compilation.
+- When Ziggy goes to build its copy of the HDF5 libraries, for some reason the HDF5 build system doesn't always find the necessary include files for building the Java API. If this happens to you, create the environment variable JNIFLAGS: `export JNIFLAGS="-I/include -I/include/"`. Here ``is the location of your JDK (so it should be identical to the contents of `JAVA_HOME`, above). The `/include` directory will have an additional subdirectory of include files that are specific to a particular OS (for example, on my Macbook this is `$JAVA_HOME/include/darwin`). With a properly-set `JNIFLAGS` environment variable, you should be able to build HDF5 without difficulty (famous last words...).
+
+Once you've got that all set up, do the following:
+
+1. Open a terminal window.
+2. Change directory to the Ziggy main directory (the one that looks like the figure above).
+3. At the command line, type `./gradlew`.
+
+The first time you do this, it will take a long time to run and there will be a lot of C and C++ compiling. This is because Gradle is building the HDF5 libraries, which can take a long time. Gradle will also download all the third party libraries and Jarfiles Ziggy relies upon. Barring the unforeseen, eventually you should see something like this in your terminal:
+
+```console
+ziggy$ ./gradlew
+ar: creating archive ziggy/build/lib/libziggy.a
+ar: creating archive ziggy/build/lib/libziggymi.a
+
+> Task :compileJava
+Note: Some input files use or override a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+
+
+BUILD SUCCESSFUL in 14s
+19 actionable tasks: 19 executed
+ziggy$
+```
+
+At this point, it's probably worthwhile to run Ziggy's unit tests to make sure nothing has gone wrong. To do this, at the command line type `./gradlew test`. The system will run through a large number of tests (around 700) over the course of about a minute, and hopefully you'll get another "BUILD SUCCESSFUL" message at the end.
+
+If you look at the Ziggy folder now, you'll see the following:
+
+```console
+ziggy$ ls
+LICENSE.pdf buildSrc gradle.properties sample-pipeline test
+README.md doc gradlew script-plugins
+build etc ide settings.gradle
+build.gradle gradle licenses src
+ziggy$
+```
+
+Pretty much the same, except that now there's a `build` folder. What's in the `build` folder?
+
+```console
+build$ ls
+bin etc lib obj schema tmp
+classes include libs resources src
+build$
+```
+
+The main folders of interest here are:
+
+- The `bin` folder, which has all the executables.
+- The `lib` folder, which has shared object libraries (i.e., compiled C++)
+- The `libs` folder, which has Jarfiles (so why not name it "jars"? I don't know).
+- The `etc` folder, which is a copy of the main `ziggy/etc` folder.
+
+Everything that Ziggy uses in execution comes from the subfolders of `build`. That's why there's a copy of `etc` in `build`: the `etc` in the main directory isn't used, the one in `build` is, so that everything that Ziggy needs is in one place.
+
+Before we move on, a few useful details about building Ziggy:
+
+- Ziggy makes use of a number of dependencies. Most of them are Jarfiles, which means that they can be downloaded and used without further ado, but at least one, the [HDF5 library](https://www.hdfgroup.org/solutions/hdf5/), requires compilation via the C++ compiler. This is the most time-consuming part of the build.
+- The first time you run `./gradlew`, the dependencies will be automatically downloaded. On subsequent builds with `./gradlew`, the dependencies will mostly not be downloaded, but instead cached copies will be used. This means that the subsequent uses are much faster.
+- Why "mostly not ... downloaded?" Well, the build system checks the dependencies to see whether any new versions have come out. New versions of the third party libraries are automatically downloaded in order to ensure that Ziggy remains up-to-date with security patches. So on any given invocation of `./gradlew`, there might be a library or two that gets updated.
+- Gradle has lots of commands (known in the lingo as "tasks") other than `test`. Most notably, the `./gradlew clean` command will delete the build directory so you can start the build over from the beginning. The `./gradlew build` command will first build the code and then run the unit tests.
+
+If all is going well, you're now ready to move on to [defining your own pipeline](configuring-pipeline.md)!
diff --git a/doc/user-manual/event-handler-basics.md b/doc/user-manual/event-handler-basics.md
new file mode 100644
index 0000000..eefe56d
--- /dev/null
+++ b/doc/user-manual/event-handler-basics.md
@@ -0,0 +1,196 @@
+## Event Handler Basics
+
+So far we've talked about Ziggy pipelines as being completely driven by human-in-the-loop control: a pipeline operator is required to launch any pipelines, after which the pipeline runs without further human assistance until it either completes or fails. This is acceptable at the scale of the sample pipeline that ships with Ziggy for demonstration purposes. As the volume of data increases, and more relevantly as the frequency of data deliveries increases, this becomes less desirable. At some data volume it becomes essential for Ziggy to have its own system for determining that some action is required and initiating that action without any help from a human.
+
+Fortunately, Ziggy has this capability in the form of its event handler mechanism.
+
+Note that, for the exercises in this section, I've reset the sample pipeline back to its initial state. The commands for that are as follows:
+
+```
+runjava cluster stop
+rm -rf sample-pipeline/build
+/bin/bash sample-pipeline/build-env.sh
+runjava cluster init
+runjava cluster start gui &
+```
+
+### What it Does
+
+To get a sense of how the event handler works, do the following:
+
+First, get the data receipt directory set up with content. If you followed my suggestion to reset the pipeline back to its initial state, you don't have to do anything further. If you've rejected my suggestion, then you have to copy all the files in `sample-pipeline/data` to `sample-pipeline/build/data-receipt`. As a reminder, the `sample-pipeline/build/data-receipt` directory should look like this when you're done:
+
+```console
+data-receipt$ ls -1
+nasa_logo-set-1-file-0.png
+nasa_logo-set-1-file-1.png
+nasa_logo-set-1-file-2.png
+nasa_logo-set-1-file-3.png
+nasa_logo-set-2-file-0.png
+nasa_logo-set-2-file-1.png
+nasa_logo-set-2-file-2.png
+nasa_logo-set-2-file-3.png
+sample-model.txt
+sample-pipeline-manifest.xml
+data-receipt$
+```
+
+Now that you've done that, start the pipeline and the console (if they aren't already running); from the `Configuration` tab select `Event Definitions`. You'll see a table like this appear:
+
+![](images/event-defs-display.png)
+
+As shown, an event definition has a name, a directory, a pipeline, and a toggle that enables or disables the event handler. Right now this one is turned off. As you might have inferred from this, when this event handler is turned on, it watches the `sample-pipeline/build/data-receipt` directory for an event; when it sees an event, it starts the `sample` pipeline. At its essence, that's all a Ziggy event handler is: a tool that watches a specified directory and then starts a specified pipeline.
+
+Next step: click the check-box under `Enabled`. Wait for 10-15 seconds, and --
+
+-- nothing happens. The pipeline doesn't start, the `Pi` stoplight doesn't turn green. Nothing.
+
+If you read the preceding paragraphs carefully, you'll see that we left something out. We said that an event handler watches a directory for an event, and it then starts a pipeline. We never defined what constitutes an event! Clearly, it's not just the presence of files in the watched directory: there are files in the watched directory, and the event handler isn't interested.
+
+### What is an Event?
+
+If you think about it, you'll soon realize that it wouldn't be safe for Ziggy to simply start running whenever files show up in the watched directory. For one thing, what would stop Ziggy from starting the pipeline while the files it needs are still being put into the watched directory? For another, what if there are things happening that need to be treated as multiple different events? For another, what if Ziggy needs more information than just, "Go do something"?
+
+To solve these problems, we defined an event signal, known as a ready file, as follows:
+
+**A Ziggy ready file is a zero-length file, where the filename contains all the information Ziggy needs to know how it should respond to the event.**
+
+More specifically, the filename convention for a ready file is as follows:
+
+```
+.READY..
+```
+
+The `name` is, well, the name of the event. At any given time there can be ready files with different names in the directory; these correspond to different events. Each different event gets its own pipeline instance.
+
+The `label` provides additional information to the event handler. We'll see how this works shortly. The main thing is that the combination of `name` and `label` need to be unique. If you have outside users doing things that create ready files, make sure that they have name conventions that ensure that this restriction is met!
+
+The `count` indicates how many ready files to expect for a given event `name`. What does this mean? Imagine that somebody, somewhere is generating an event named `tin-machine`, and the following ready files are delivered:
+
+```
+hunt.READY.tin-machine.4
+tony.READY.tin-machine.4
+reeves.READY.tin-machine.4
+```
+
+There are 3 ready files for `tin-machine`, but because the `count` portion of the filename is 4, Ziggy knows that it shouldn't start the pipeline yet because there's a fourth ready file needed before the `tin-machine` event is complete and ready to be handled. Once the ready file `david.READY.tin-machine.4` appears, Ziggy knows that it can start the pipeline in response to the `tin-machine` event.
+
+#### Why Have More Than One Ready File?
+
+In the discussion above, we explained how to take a bunch of ready files that have different labels and group them together to represent the same event. We also showed how Ziggy knows how many ready files to expect for a given event, and that Ziggy won't start the pipeline for that event until all the ready files are present.
+
+All of that is fine, but we haven't yet explained why you would want to have more than one ready file per event in the first place! Rather than answer that question immediately, I'm going to beg your indulgence until after we get through the simple example, below, and the more complicated example, even further below.
+
+### What Happens When the Ready File Appears
+
+#### Why Have More Than One Ready File?
+
+In the discussion above, we explained how to take a bunch of ready files that have different labels and group them together to represent the same event. We also showed how Ziggy knows how many ready files to expect for a given event, and that Ziggy won't start the pipeline for that event until all the ready files are present.
+
+All of that is fine, but we haven't yet explained why you would want to have more than one ready file per event in the first place! Rather than answer that question immediately, I'm going to beg your indulgence until after we get through the simple example, below, and the more complicated example, even further below.
+
+### What Happens When the Ready File Appears
+
+To see this in action, do the following:
+
+- First, go to the `Event Definitions` panel and turn off the `data-receipt` event handler (i.e., make sure that the check-box is not checked).
+- Next, in a terminal window `cd` to the `sample-pipeline/build/data-receipt` directory, and type the following: `touch null.READY.blue-jean.1`. If you list the files in the directory you should see the one you just created, `null.READY.blue-jean.1`.
+- Finally, on the `Event Definitions` panel, check the `enabled` box for the `data-receipt` event handler.
+
+After a few seconds, the `Pi` stoplight will turn green. If you now go to the `Instances` tab from the `Operations` tab, you'll see something like this:
+
+TODO Explain the numbers in the UOW column. Ah, I just reminded myself that they might come from the taskDirectoryRegex in the subtask configuration parameters. Might be worth a reminder and cross-reference here (and elsewhere in this article) since it's been a while.
+
+![](images/blue-jean-event.png)
+
+The pipeline is running! The name of the pipeline instance is the pipeline name, plus the name of the event handler that started processing, plus the time at which processing started (in ISO8601 format).
+
+Now to back to the `Configuration` tab, this time choose `Pipeline Events` from the menu on the left:
+
+![](images/event-display.png)
+
+Every event, from every handler, is shown here.
+
+At this point you'll also see that the `sample-pipeline/build/data-receipt` directory is empty. The data files and model file have been imported, the manifest and its acknowledgement moved to their permanent location, and the ready file has been deleted as well. This prevents a stale ready file from accidentally launching more pipeline instances!
+
+### A More Complicated Example
+
+For the next example, we need to take a couple of steps in preparation.
+
+First, copy the contents of the `multi-data` directory to `data-receipt`:
+
+```console
+ziggy$ cp -r sample-pipeline/multi-data/* sample-pipeline/build/pipeline-results/data-receipt
+```
+
+Take a look at the resulting contents of data-receipt: there are now 3 subdirectories, each of which has files and a manifest. If you look carefully, you'll see that the `sample-3` directory isn't ready for import: its manifest lists 8 files, but only 7 are present. This simulates a situation in which files are being delivered, the delivery is not complete, and for whatever reason the manifest for `sample-3` arrived before the `sample-3` files for import did. This example will show that parts of a delivery can be processed while other parts are still being delivered.
+
+Next: go to `Configuration`, select `Parameter Library`, and double-click on the `Data receipt configuration` parameter set. Edit the taskDirectoryRegex parameter (TODO Describe how to edit it and what to change it to) and save the parameter set:
+
+
+
+Next, edit the `Multiple subtask configuration` and `Single subtask configuration` parameter sets. In both cases, make sure that `reprocess` is unchecked.
+
+Finally, go back to the `Event Definitions` panel and make sure that data-receipt is still `enabled`.
+
+Now! We are ready to start the example. In a terminal window, `cd` to `sample-pipeline/build/pipeline-results/data-receipt`. Enter the following at the terminal window prompt:
+
+```
+touch sample-1.READY.glass-spider.2
+touch sample-2.READY.glass-spider.2
+```
+
+Wait a few seconds, and you'll see something like this:
+
+![](images/data-receipt-2-task-event.png)
+
+Ziggy generated two data-receipt tasks: one for sample-1, the other for sample-2 . Once these had run, the pipeline continued, generating and running tasks to process the newly-imported data. The `sample-pipeline/build/data-receipt` now contains only the `sample-3` directory, which was not included in the import. Meanwhile, because there's no new data for `set-1` or `set-2`, we see Ziggy generate tasks for those units of work, but the tasks have no subtasks and so they complete instantly.
+
+TODO Regarding the last sentence about set-*:
+1. Add the task IDs associated with set-[12] in parens so that we can identify them.
+2. A reminder of the numbers in the UOW column is in order.
+3. It might be a good time to remind the reader, notably me, about how the permuter determined its tasks. A naive reader might assume that since the set-1 and set-2 files had already been processed, they wouldn't be processed again, but they kinda sorta were.
+4. Ah, I figured it out. The permuter uses the "raw data" type and that regex is applied in the sample subdirectories, right? That also means that UOW is coming from the number in the set-# name, right? But there isn't a grouping for just the number, so a reminder of all this is in order here.
+5. Explain or remind how Ziggy determined that there wasn't any new set-[12] data.
+
+What we're simulating here is a situation in which the `sample-1` and `sample-2` data receipt subdirectories were part of one data delivery activity, while `sample-3` is part of another. Once both `sample-1` and `sample-2` were complete, and their ready files delivered, Ziggy knew that those subdirectories were complete, that they were part of a single event, and that there was nothing else in that event that Ziggy needed to wait for, so it could start processing those subdirectories.
+
+If this were happening in real life, once the `sample-3` subdirectory was complete, the system that delivers files to Ziggy could create a new ready file, something like `sample-3.READY.spaceboy.1`, in the data receipt directory. That would signal to Ziggy to start processing the contents of the `sample-3` subdirectory.
+
+From this, we see that there are a couple of reasons to have multiple ready files in a single event:
+
+1. The ready file labels contain information that Ziggy may want or need. For example, data receipt uses the labels to figure out which data receipt subdirectories belong to a single delivery. In order to tell Ziggy that `sample-1` and `sample-2` are 2 subdirectories that are part of the same delivery, we need 2 ready files so that both labels can be supplied.
+2. For complicated events, such as data deliveries, the multiple ready files can provide information to the user about the status and progress of the event. For example, by looking at the data receipt directory, the user can tell which directories are ready to be processed and which ones are part of a given event (i.e., you can tell that `sample-3` is not part of the event that contains `sample-1` and `sample-2`, even though the directory names might make you think they're all part of the same event). Alternately, if the user looked and saw subdirectories `sample-1`, `sample-2`, and `sample-3`, but only saw the single ready file `sample-2.READY.blue-jean.2`, they would know that the delivery was ultimately going to contain 2 subdirectories, but that only `sample-2` is complete so far, and that one of the three subdirectories belongs to another, distinct event.
+
+What we're simulating here is a situation in which the `sample-1` and `sample-2` data receipt subdirectories were part of one data delivery activity, while `sample-3` is part of another. Once both `sample-1` and `sample-2` were complete, and their ready files delivered, Ziggy knew that those subdirectories were complete, that they were part of a single event, and that there was nothing else in that event that Ziggy needed to wait for, so it could start processing those subdirectories.
+
+If this were happening in real life, once the `sample-3` subdirectory was complete, the system that delivers files to Ziggy could create a new ready file, something like `sample-3.READY.spaceboy.1`, in the data receipt directory. That would signal to Ziggy to start processing the contents of the `sample-3` subdirectory.
+
+From this, we see that there are a couple of reasons to have multiple ready files in a single event:
+
+1. The ready file labels contain information that Ziggy may want or need. For example, data receipt uses the labels to figure out which data receipt subdirectories belong to a single delivery. In order to tell Ziggy that `sample-1` and `sample-2` are 2 subdirectories that are part of the same delivery, we need 2 ready files so that both labels can be supplied.
+2. For complicated events, such as data deliveries, the multiple ready files can provide information to the user about the status and progress of the event. For example, by looking at the data receipt directory, the user can tell which directories are ready to be processed and which ones are part of a given event (i.e., you can tell that `sample-3` is not part of the event that contains `sample-1` and `sample-2`, even though the directory names might make you think they're all part of the same event). Alternately, if the user looked and saw subdirectories `sample-1`, `sample-2`, and `sample-3`, but only saw the single ready file `sample-2.READY.blue-jean.2`, they would know that the delivery was ultimately going to contain 2 subdirectories, but that only `sample-2` is complete so far, and that one of the three subdirectories belongs to another, distinct event.
+
+### Getting Label Information to Algorithms
+
+Depending on your needs, you may want the pipeline that responds to an event to get information about the event like the event name and labels. This is straightforward.
+
+If you go back to the article on [configuring a pipeline](configuring-pipeline.md), and swim down to the section on "glue" code, you can see that every subtask gets an HDF5 file that includes, among other things, all the module parameters for that subtask. In the `moduleParameters` field, you'll find one named `ZiggyEventLabels` (that is, you'll find it if the pipeline was started by an eent handler; if the pipeline was started manually there won't be any such parameter set). This parameter set will include the name of the event, the name of the pipeline, and all of the event labels that were part of that event when the pipeline launched.
+
+TODO Ah, this sort of answers my question about how labels are used. Suggest an example or two of how the pipeline could use the labels. What makes a good label? Why wouldn't you create a label called sldj? Discussing this up front is likely to drive the next version of this article.
+
+#### The null Label
+
+TODO The next version will discuss use cases. One of those cases will be copying data files directly into the data receipt directory. In this case, the label is not needed, so both it and the following dot can be removed, so that the ready file is READY.scary-monsters.1, for example.
+
+If you go back up to the original example, in which all the files for import were in the main data receipt directory, you might have noticed that the ready file's label was `null`. This is a special case label: it tells Ziggy that there's no actual label information that it's going to need, all it needs is the ready file itself. When Ziggy's event handler infrastructure sees a ready file with a `null` label, it translates that internally into an empty string.
+
+In that particular case, we needed to use `null` because remember that the data receipt event handler, and the data receipt pipeline module, translate the labels into the names of data receipt subdirectories! In that case, since we were using the main directory and not subdirectories, we needed to signal to the event handler and the data receipt pipeline module that we didn't want it looking for subdirectories.
+
+Because ready files need to be unique to the file system, you can't have an event that has two ready files with `null` labels: they would have the same filename, so one would overwrite the other. In general this means that you can only use the `null` label when the event has only 1 ready file associated with it. It's possible to use a `null` label and then other ready files with non-`null` labels, though I have trouble imagining that there would ever be a good reason for that.
+
+As an aside: you may ask, "Why not just leave the label blank in the ready file?" That is, why not a ready file that looks like `.READY.scary-monsters.1`, for example? Unfortunately, a file that starts with a dot is an "invisible" file in both Linux and Mac file systems, which means that it wouldn't show up in a directory listing. We wanted to avoid this outcome (where usually the ready files are visible, but in this case it would not be), thus the `null` label.
+
+### How to Define an Event Handler
+
+There's a whole separate article on [Defining Event Handlers](event-handler-definition.md).
diff --git a/doc/user-manual/event-handler-definition.md b/doc/user-manual/event-handler-definition.md
new file mode 100644
index 0000000..b7e8364
--- /dev/null
+++ b/doc/user-manual/event-handler-definition.md
@@ -0,0 +1,23 @@
+## Defining Event Handlers
+
+Defining an event handler is surprisingly easy. At least, it's surprising to me. Maybe it will be to you as well.
+
+### The Event Handler XML File
+
+You probably already guessed that it would be an XML file. The event handler XML files, by convention, have names that start with `pe-`.
+
+The event handler demonstrated in the sample pipeline is in [pe-sample.xml](../../sample-pipeline/config/pe-sample.xml). Leaving aside the boilerplate that starts and ends the XML file, here's the event definition in all its glory:
+
+```xml
+
+```
+
+ The `pipelineEvent` element straightforwardly defines the name of the event handler itself and the name of the pipeilne it triggers. The `enableOnClusterStart` attribute tells Ziggy whether the event handler should be enabled immediately when you type `runjava cluster start`, or whether it should require a human to go in and turn it on via the console after the cluster is already up and running.
+
+The `directory` attribute is, of course, the directory where the event handler looks for its ready files. In this case, the data receipt event handler looks for ready files in the same directory where the data receipt pipeline module looks for files (or directories of files). That was a design choice, though you wouldn't need to do it that way: you could have a totally separate directory for the event handler to watch, if such was your preference.
+
+Note here that, rather than a normal string, the directory attribute can take a string that needs to be expanded into [one of Ziggy's properties](properties.md). This allows you to specify all the watched directories in the properties file. Note **that this is the only attribute or element in all of Ziggy's XML infrastructure that allows the use of property expansion notation!** That's strictly because it's the only place where we thought we needed it. If you need this added to some other part of the XML infrastructure for your purposes, let us know. We can make it happen!
+
+TODO Consider allowing property interpolation more generally (ZIGGY-173: Add property interpolation to XML files) and say so here. In addition, it would also be good to talk about properties in XML files in the "Write the Pipeline Configuration Files" section of configuring-pipeline.md and to mention that you have to restart the cluster (which we haven't done yet, but will soon, we promise!)
diff --git a/doc/user-manual/hpc-cost.md b/doc/user-manual/hpc-cost.md
new file mode 100644
index 0000000..479c22e
--- /dev/null
+++ b/doc/user-manual/hpc-cost.md
@@ -0,0 +1,15 @@
+## HPC Cost Estimation
+
+The Remote Parameters Dialog allows you to get a prior estimate of the cost of processing a task on HPC. This is great, but a post-run estimate is also valuable. The post-run can differ from the pre-run for two reasons: First, the pre-run estimate might have been an overestimate, in which case the jobs would finish ahead of schedule and the true cost would be lower; alternately, if the jobs all ran out of wall time and the task was resubmitted, then the total cost will be higher than the pre-run estimate.
+
+Ziggy keeps a running track of the costs of all jobs associated with a task and can display them for the user. Note that even after the tasks have finished, the costs are considered estimates. This is because they don't actually tap into any job accounting system at the HPC facility, which would provide a definitive cost (i.e., what you were actually charged). Instead, Ziggy computes the cost by considering the execution time of each job and its node type. Since this isn't the definitive value, we always call it an estimate.
+
+Anyway:
+
+### Viewing the HPC Cost Estimates
+
+Go to the Instances tab, right-click on an instance, and select the Cost estimate option. You'll get a new dialog box:
+
+![](images/hpc-cost-est.png)
+
+In our case, we ran all our tasks on the local system so all the estimates are zero! However: if you'd run on an HPC system, the `Cost Estimate` column would show the running total for each task during execution, and would show a final estimate after execution completed.
\ No newline at end of file
diff --git a/doc/user-manual/images/architecture-diagram.png b/doc/user-manual/images/architecture-diagram.png
new file mode 100644
index 0000000..af65af9
Binary files /dev/null and b/doc/user-manual/images/architecture-diagram.png differ
diff --git a/doc/user-manual/images/blue-jean-event.png b/doc/user-manual/images/blue-jean-event.png
new file mode 100644
index 0000000..1b3ded5
Binary files /dev/null and b/doc/user-manual/images/blue-jean-event.png differ
diff --git a/doc/user-manual/images/data-receipt-2-task-event.png b/doc/user-manual/images/data-receipt-2-task-event.png
new file mode 100644
index 0000000..1a91923
Binary files /dev/null and b/doc/user-manual/images/data-receipt-2-task-event.png differ
diff --git a/doc/user-manual/images/data-receipt-display.png b/doc/user-manual/images/data-receipt-display.png
new file mode 100644
index 0000000..a638092
Binary files /dev/null and b/doc/user-manual/images/data-receipt-display.png differ
diff --git a/doc/user-manual/images/data-receipt-list.png b/doc/user-manual/images/data-receipt-list.png
new file mode 100644
index 0000000..5c6c1e7
Binary files /dev/null and b/doc/user-manual/images/data-receipt-list.png differ
diff --git a/doc/user-manual/images/data-receipt-param-set.png b/doc/user-manual/images/data-receipt-param-set.png
new file mode 100644
index 0000000..0099172
Binary files /dev/null and b/doc/user-manual/images/data-receipt-param-set.png differ
diff --git a/doc/user-manual/images/delete-alert.png b/doc/user-manual/images/delete-alert.png
new file mode 100644
index 0000000..b3898f9
Binary files /dev/null and b/doc/user-manual/images/delete-alert.png differ
diff --git a/doc/user-manual/images/delete-in-progress.png b/doc/user-manual/images/delete-in-progress.png
new file mode 100644
index 0000000..3cdfc3c
Binary files /dev/null and b/doc/user-manual/images/delete-in-progress.png differ
diff --git a/doc/user-manual/images/delete-task-menu-item.png b/doc/user-manual/images/delete-task-menu-item.png
new file mode 100644
index 0000000..7e10e21
Binary files /dev/null and b/doc/user-manual/images/delete-task-menu-item.png differ
diff --git a/doc/user-manual/images/edit-array-1.png b/doc/user-manual/images/edit-array-1.png
new file mode 100644
index 0000000..c070ff1
Binary files /dev/null and b/doc/user-manual/images/edit-array-1.png differ
diff --git a/doc/user-manual/images/edit-array-2.png b/doc/user-manual/images/edit-array-2.png
new file mode 100644
index 0000000..016498b
Binary files /dev/null and b/doc/user-manual/images/edit-array-2.png differ
diff --git a/doc/user-manual/images/edit-param-set.png b/doc/user-manual/images/edit-param-set.png
new file mode 100644
index 0000000..c41ccbd
Binary files /dev/null and b/doc/user-manual/images/edit-param-set.png differ
diff --git a/doc/user-manual/images/edit-trigger.png b/doc/user-manual/images/edit-trigger.png
new file mode 100644
index 0000000..442dd30
Binary files /dev/null and b/doc/user-manual/images/edit-trigger.png differ
diff --git a/doc/user-manual/images/event-defs-display.png b/doc/user-manual/images/event-defs-display.png
new file mode 100644
index 0000000..90b8f6f
Binary files /dev/null and b/doc/user-manual/images/event-defs-display.png differ
diff --git a/doc/user-manual/images/event-display.png b/doc/user-manual/images/event-display.png
new file mode 100644
index 0000000..b785f73
Binary files /dev/null and b/doc/user-manual/images/event-display.png differ
diff --git a/doc/user-manual/images/exception-1.png b/doc/user-manual/images/exception-1.png
new file mode 100644
index 0000000..3b3b7d8
Binary files /dev/null and b/doc/user-manual/images/exception-1.png differ
diff --git a/doc/user-manual/images/exception-2.png b/doc/user-manual/images/exception-2.png
new file mode 100644
index 0000000..a67ecb8
Binary files /dev/null and b/doc/user-manual/images/exception-2.png differ
diff --git a/doc/user-manual/images/fire-trigger.png b/doc/user-manual/images/fire-trigger.png
new file mode 100644
index 0000000..ad44882
Binary files /dev/null and b/doc/user-manual/images/fire-trigger.png differ
diff --git a/doc/user-manual/images/flip-tasks.png b/doc/user-manual/images/flip-tasks.png
new file mode 100644
index 0000000..e914043
Binary files /dev/null and b/doc/user-manual/images/flip-tasks.png differ
diff --git a/doc/user-manual/images/gui-start-end-adjusted.png b/doc/user-manual/images/gui-start-end-adjusted.png
new file mode 100644
index 0000000..11f475b
Binary files /dev/null and b/doc/user-manual/images/gui-start-end-adjusted.png differ
diff --git a/doc/user-manual/images/gui.png b/doc/user-manual/images/gui.png
new file mode 100644
index 0000000..421a983
Binary files /dev/null and b/doc/user-manual/images/gui.png differ
diff --git a/doc/user-manual/images/hpc-cost-est.png b/doc/user-manual/images/hpc-cost-est.png
new file mode 100644
index 0000000..fba059c
Binary files /dev/null and b/doc/user-manual/images/hpc-cost-est.png differ
diff --git a/doc/user-manual/images/instances-running.png b/doc/user-manual/images/instances-running.png
new file mode 100644
index 0000000..9f11bba
Binary files /dev/null and b/doc/user-manual/images/instances-running.png differ
diff --git a/doc/user-manual/images/logs-list.png b/doc/user-manual/images/logs-list.png
new file mode 100644
index 0000000..8f64f13
Binary files /dev/null and b/doc/user-manual/images/logs-list.png differ
diff --git a/doc/user-manual/images/monitor-processes.png b/doc/user-manual/images/monitor-processes.png
new file mode 100644
index 0000000..9d97715
Binary files /dev/null and b/doc/user-manual/images/monitor-processes.png differ
diff --git a/doc/user-manual/images/monitoring-alerts.png b/doc/user-manual/images/monitoring-alerts.png
new file mode 100644
index 0000000..cb88cb5
Binary files /dev/null and b/doc/user-manual/images/monitoring-alerts.png differ
diff --git a/doc/user-manual/images/monitoring-worker.png b/doc/user-manual/images/monitoring-worker.png
new file mode 100644
index 0000000..5c65389
Binary files /dev/null and b/doc/user-manual/images/monitoring-worker.png differ
diff --git a/doc/user-manual/images/ops-panel.png b/doc/user-manual/images/ops-panel.png
new file mode 100644
index 0000000..56c3b0a
Binary files /dev/null and b/doc/user-manual/images/ops-panel.png differ
diff --git a/doc/user-manual/images/parameter-library.png b/doc/user-manual/images/parameter-library.png
new file mode 100644
index 0000000..7df6d81
Binary files /dev/null and b/doc/user-manual/images/parameter-library.png differ
diff --git a/doc/user-manual/images/permuter-tasks.png b/doc/user-manual/images/permuter-tasks.png
new file mode 100644
index 0000000..b662fe9
Binary files /dev/null and b/doc/user-manual/images/permuter-tasks.png differ
diff --git a/doc/user-manual/images/pipeline-done.png b/doc/user-manual/images/pipeline-done.png
new file mode 100644
index 0000000..51d2718
Binary files /dev/null and b/doc/user-manual/images/pipeline-done.png differ
diff --git a/doc/user-manual/images/pipeline-simplified.png b/doc/user-manual/images/pipeline-simplified.png
new file mode 100644
index 0000000..173af15
Binary files /dev/null and b/doc/user-manual/images/pipeline-simplified.png differ
diff --git a/doc/user-manual/images/remote-dialog-1.png b/doc/user-manual/images/remote-dialog-1.png
new file mode 100644
index 0000000..bbb8a71
Binary files /dev/null and b/doc/user-manual/images/remote-dialog-1.png differ
diff --git a/doc/user-manual/images/remote-dialog-2.png b/doc/user-manual/images/remote-dialog-2.png
new file mode 100644
index 0000000..bc869cc
Binary files /dev/null and b/doc/user-manual/images/remote-dialog-2.png differ
diff --git a/doc/user-manual/images/remote-dialog-3.png b/doc/user-manual/images/remote-dialog-3.png
new file mode 100644
index 0000000..5e0d92b
Binary files /dev/null and b/doc/user-manual/images/remote-dialog-3.png differ
diff --git a/doc/user-manual/images/remote-dialog-4.png b/doc/user-manual/images/remote-dialog-4.png
new file mode 100644
index 0000000..f34f760
Binary files /dev/null and b/doc/user-manual/images/remote-dialog-4.png differ
diff --git a/doc/user-manual/images/remote-dialog-5.png b/doc/user-manual/images/remote-dialog-5.png
new file mode 100644
index 0000000..788956f
Binary files /dev/null and b/doc/user-manual/images/remote-dialog-5.png differ
diff --git a/doc/user-manual/images/restart-dialog.png b/doc/user-manual/images/restart-dialog.png
new file mode 100644
index 0000000..656d123
Binary files /dev/null and b/doc/user-manual/images/restart-dialog.png differ
diff --git a/doc/user-manual/images/start-end-nodes.png b/doc/user-manual/images/start-end-nodes.png
new file mode 100644
index 0000000..41bb409
Binary files /dev/null and b/doc/user-manual/images/start-end-nodes.png differ
diff --git a/doc/user-manual/images/task-log-display.png b/doc/user-manual/images/task-log-display.png
new file mode 100644
index 0000000..95c89ab
Binary files /dev/null and b/doc/user-manual/images/task-log-display.png differ
diff --git a/doc/user-manual/images/task-log-showing-problem.png b/doc/user-manual/images/task-log-showing-problem.png
new file mode 100644
index 0000000..d29152c
Binary files /dev/null and b/doc/user-manual/images/task-log-showing-problem.png differ
diff --git a/doc/user-manual/images/tasks-done.png b/doc/user-manual/images/tasks-done.png
new file mode 100644
index 0000000..31f47d3
Binary files /dev/null and b/doc/user-manual/images/tasks-done.png differ
diff --git a/doc/user-manual/images/tasks-menu.png b/doc/user-manual/images/tasks-menu.png
new file mode 100644
index 0000000..afeefbe
Binary files /dev/null and b/doc/user-manual/images/tasks-menu.png differ
diff --git a/doc/user-manual/images/triggers.png b/doc/user-manual/images/triggers.png
new file mode 100644
index 0000000..05b749e
Binary files /dev/null and b/doc/user-manual/images/triggers.png differ
diff --git a/doc/user-manual/images/ziggy-small-clear.png b/doc/user-manual/images/ziggy-small-clear.png
new file mode 100644
index 0000000..b6cb6b1
Binary files /dev/null and b/doc/user-manual/images/ziggy-small-clear.png differ
diff --git a/doc/user-manual/instances-panel.md b/doc/user-manual/instances-panel.md
new file mode 100644
index 0000000..473097e
--- /dev/null
+++ b/doc/user-manual/instances-panel.md
@@ -0,0 +1,207 @@
+## The Instances Panel
+
+The instances panel is the single most useful part of Ziggy when it comes to monitoring execution, so it's worth some discussion of exactly what it's trying to tell you. In the process, we'll introduce some concepts that will remain vital as we move through Ziggy-land.
+
+### Instances, Tasks, and Subtasks
+
+The first thing to explain is the concept of instances, tasks, and subtasks. Instances are the things shown on the left side of the display; tasks are on the right; the numbers in the last column of the tasks table represent counts of subtasks.
+
+And now you know as much as you did when we started. Okay, keep reading.
+
+#### Instances
+
+A pipeline instance is just what it sounds like: it's a single instance of one of the pipelines defined in the `pd-*.xml` files. Recall that the sample pipeline has 4 nodes: data receipt, permuter, flip, and averaging. When you pressed the `Fire!` button, Ziggy created a copy of that pipeline, in a form that Ziggy knows how to execute.
+
+Each instance has a unique ID number, with instance 1 being the first (so 1-based, not 0-based). The instance contains its own copies of all the parameter sets used in the pipeline, and these are stored permanently. Thus, even if you later change the values of some parameters, the copies that were made for the instance won't change.
+
+#### Tasks
+
+A pipeline task is also just what it sounds like: it's a chunk of work that Ziggy has to execute as part of a pipeline instance. Each task uses a specific algorithm module to process a specific collection of data. Tasks, then, are the things that pipeline instances use to run the individual algorithms. Every task is associated with one and only one instance; an instance can have as many tasks as it needs to get the work done.
+
+In the same way that a pipeline instance is defined by a `pipeline` definition in one of the `pd-*.xml` files, each task is created from a `node` in the pipeline. As the pipeline instance executes, it steps through the node list; at each node, it creates tasks that run the algorithm defined by the node; once those tasks are done, the instance moves on to the next node until the tasks created from the last node finish.
+
+Like pipeline instances, pipeline tasks have unique ID numbers that start at 1 and increase monotonically. Task numbers are never "recycled." That is to say, if pipeline instance 1 has tasks 1 through 7, pipeline instance 2 will start with task 8.
+
+#### Subtasks
+
+It turns out that, in many (perhaps most) cases, data analysis is what we call "embarrassingly parallel:" that is to say, there are a lot of nuggets of processing that can be performed in parallel with one another, that do not need to interact with each other at any time, and which run the same code against different chunks of data. In cases like this, there's a big execution time advantage if the nuggets are run simultaneously with one another to the extent that the compute hardware will allow.
+
+These nuggets are known in Ziggy-land as subtasks.
+
+Subtasks, confusingly, are numbered from zero. Also, subtask numbers are "recycled:" that is to say, if one task has subtasks 0 to N, another will have subtasks 0 to M. The subtask numbers appear in the subdirectories of the task directory as `st-0` through `st-`.
+
+With all that in mind, let's look at the instances panel again:
+
+![](images/pipeline-done.png)
+
+The instance on the left, instance 1, is the pipeline instance that's going to plow through the entire pipeline, from data receipt to averaging. On the right, we see the tasks that instance 1 uses for the processing: one task for data receipt, two each for permuter, flip, and averaging. The numbers in the `P-state` column represent subtask counts: the first number is total number of subtasks, the second is number completed, the third is number failed. Each of permuter and flip used 4 subtasks per task; averaging ran with just 1 subtask per task.
+
+The scoreboard at the top rolls up the tasks table according to algorithm name. The permuter line shows the aggregated results of the 2 permuter tasks, and so on. The final line is the roll-up across all tasks within the pipeline. The scoreboard presents the task information slightly differently in that it shows totals of `Submitted`, `Processing`, `Completed`, and `Failed`. `Completed` and `Failed` are self-explanatory (I hope). `Processing` indicates tasks that are currently using computer time to process their data. `Submitted` tasks are tasks that are waiting for some hardware, somewhere, to decide to process them. Depending on your system, tasks may go instantly from `Submitted` to `Processing`, or some of them might have to wait around awhile in the `Submitted` queue.
+
+### Unit of Work
+
+In the midst of all this is a column under tasks labeled `UOW`, which stands for "Unit of Work." As a general matter, "Unit of Work" is a $10 word for, "What's the chunk of data that this task is in charge of?"
+
+The parameter set that Ziggy uses to figure out how to divide work up into tasks also provides a means by which the user can specify a name that gets associated with each task. This is what's displayed in the `UOW` column. In the event that some tasks for a given algorithm module succeed and others fail, the `UOW` label lets you figure out where Ziggy got the data that caused the failed task. This can be useful, as I'm sure you can imagine.
+
+#### Can You be a Bit More Specific About That?
+
+Sure! Let's look again at the definition of the permuter node from [The Pipeline Definition article](pipeline-definition.md):
+
+```xml
+
+
+
+
+
+
+
+```
+
+The definition of the node includes a parameter set, `Multiple subtask configuration`, which is an instance of the `TaskConfigurationParameters`. From [the article on The Task Configuration Parameter Sets](task-configuration.md), we see that it looks like this:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+The `taskDirectoryRegex` parameter is `set-([0-9]{1})`. In plain (but New York accented) English, what this means is, "Go to the datastore and find every directory that matches the regex. Every one of those, you turn into a task. You got a problem with that?" Thus you wind up with a task for `set-1` and another for `set-2`.
+
+Meanwhile, the `taskDirectoryRegex` has a regex group in it, `([0-9]{1})`. This tells Ziggy to take that part of the directory name (i.e., a digit) and make it the name of the unit of work on the tasks table. If I had been smarter and written the `taskDirectoryRegex` as `(set-[0-9]{1})`, the UOW display would have shown `set-1` and `set-2` instead of `1` and `2`.
+
+#### What About Subtask Definition?
+
+Now we've seen how Ziggy uses the `TaskConfigurationParameters` instance to define multiple tasks for a given pipeline node. How do subtasks get defined? This uses a combination of 2 things: the `TaskConfigurationParameters` and the definition of input data file types for the node. Let's look at how that works.
+
+In TaskConfigurationParameters, there's a boolean parameter, `singleSubtask`. This does what it says: if set to `true`, Ziggy creates one and only one subtask for each task, and copies all the inputs into that subtask's directory. When set to false, as here, it generates multiple subtasks for the task.
+
+The way it does this is to create a subtask for each input data file. If we look at how the inputs to the permuter are defined in [the article on Data File Types](data-file-types.md), we see this:
+
+```xml
+
+```
+
+The file names in the datastore are going to be things like `set-1/L0/nasa-logo-file-0.png`, `set-1/L0/nasa-logo-file-1.png`, and so on. So -- cool! All the files in `set-1/L0` will be processed in the task for `set-1` data; there will be a subask for `nasa-logo-file-0.png`, another for `nasa-logo-file-1.png`, and so on.
+
+### Unit of Work
+
+In the midst of all this is a column under tasks labeled `UOW`, which stands for "Unit of Work." As a general matter, "Unit of Work" is a $10 word for, "What's the chunk of data that this task is in charge of?"
+
+The parameter set that Ziggy uses to figure out how to divide work up into tasks also provides a means by which the user can specify a name that gets associated with each task. This is what's displayed in the `UOW` column. In the event that some tasks for a given algorithm module succeed and others fail, the `UOW` label lets you figure out where Ziggy got the data that caused the failed task. This can be useful, as I'm sure you can imagine.
+
+#### Can You be a Bit More Specific About That?
+
+Sure! Let's look again at the definition of the permuter node from [The Pipeline Definition article](pipeline-definition.md):
+
+```XML
+
+
+
+
+
+
+
+```
+
+The definition of the node includes a parameter set, `Multiple subtask configuration`, which is an instance of the `TaskConfigurationParameters`. From [the article on The Task Configuration Parameter Sets](task-configuration.md), we see that it looks like this:
+
+```XML
+
+
+
+
+
+
+
+
+```
+
+The `taskDirectoryRegex` parameter is `set-([0-9]{1})`. In plain (but New York accented) English, what this means is, "Go to the datastore and find every directory that matches the regex. Every one of those, you turn into a task. You got a problem with that?" Thus you wind up with a task for `set-1` and another for `set-2`.
+
+Meanwhile, the `taskDirectoryRegex` has a regex group in it, `([0-9]{1})`. This tells Ziggy to take that part of the directory name (i.e., a digit) and make it the name of the unit of work on the tasks table. If I had been smarter and written the `taskDirectoryRegex` as `(set-[0-9]{1})`, the UOW display would have shown `set-1` and `set-2` instead of `1` and `2`.
+
+#### What About Subtask Definition?
+
+Now we've seen how Ziggy uses the `TaskConfigurationParameters` instance to define multiple tasks for a given pipeline node. How do subtasks get defined? This uses a combination of 2 things: the `TaskConfigurationParameters` and the definition of input data file types for the node. Let's look at how that works.
+
+In TaskConfigurationParameters, there's a boolean parameter, `singleSubtask`. This does what it says: if set to `true`, Ziggy creates one and only one subtask for each task, and copies all the inputs into that subtask's directory. When set to false, as here, it generates multiple subtasks for the task.
+
+The way it does this is to create a subtask for each input data file. If we look at how the inputs to the permuter are defined in [the article on Data File Types](data-file-types.md), we see this:
+
+```XML
+
+```
+
+The file names in the datastore are going to be things like `set-1/L0/nasa-logo-file-0.png`, `set-1/L0/nasa-logo-file-1.png`, and so on. So -- cool! All the files in `set-1/L0` will be processed in the task for `set-1` data; there will be a subask for `nasa-logo-file-0.png`, another for `nasa-logo-file-1.png`, and so on.
+
+### Pipeline States
+
+The instances panel also has numerous indicators named `State` and `p-State` that deserve some explanation.
+
+#### Pipeline Instance States
+
+The possible states for a pipeline instance are described below.
+
+| State | Description |
+| -------------- | ------------------------------------------------------------ |
+| INITIALIZED | Instance has been created but Ziggy hasn't yet gotten around to running any of its tasks. |
+| PROCESSING | Tasks in this instance are being processed, and none have failed yet. |
+| ERRORS_RUNNING | Tasks in this instance are being processed, but at least 1 has failed. |
+| ERRORS_STALLED | Processing has stopped because of task failures. |
+| STOPPED | Not currently used. |
+| COMPLETED | All done! |
+
+About ERRORS_RUNNING and ERRORS_STALLED: as a general matter, tasks that are running the same algorithm in parallel are totally independent, so if one fails the others can keep running; this is the ERRORS_RUNNING state. However: once all tasks for a given algorithm are done, if one or more has failed, it's not guaranteed that the next algorithm can run. After all, a classic pipeline has the outputs from one task become the inputs of the next, and in this case some of the outputs from some of the tasks aren't there. In this case, the instance goes to ERRORS_STALLED, and nothing more will happen until the operator addresses whatever caused the failure.
+
+#### Pipeline Task States
+
+The possible states for a pipeline task are described below.
+
+| State | Description |
+| ----------- | ------------------------------------------------------------ |
+| INITIALIZED | Task has been created and is waiting for some kind of attention. |
+| SUBMITTED | The task will run as soon as the worker has available resources to devote to it. |
+| PROCESSING | The task is running. |
+| ERROR | All subtasks have run, and at least one subtask has failed. |
+| COMPLETED | All subtasks completed successfully and results were copied back to the datastore. |
+| PARTIAL | Not currently used. |
+
+#### Pipeline Task Processing States (p-States)
+
+When a task is in the `PROCESSING` state, it's useful to have a more fine-grained sense of what it's doing, where it is in the process, etc. This is the role of the processing state, or `P-state`, of the task. Each `P-state` has an abbreviation that's shown in the last column of the tasks table. The `P-states` are shown below.
+
+| P-state | Abbreviation | Description |
+| -------------------- | ------------ | ------------------------------------------------------------ |
+| INITIALIZING | I | Nothing has happened yet, the task is still in the state it was in at creation time. |
+| MARSHALING | M | The inputs for the task are being assembled. |
+| ALGORITHM_SUBMITTING | As | The task is ready to run and is being sent to whatever system is in charge of scheduling its execution. |
+| ALGORITHM_QUEUED | Aq | In the case of execution environments that use a batch system, the task is waiting in the batch queue to run. |
+| ALGORITHM_EXECUTING | Ae | The algorithm is running, data is getting processed. |
+| ALGORITHM_COMPLETE | Ac | The algorithm is done running. |
+| STORING | S | Ziggy is storing results in the datastore. Sometimes referred to as "persisting." |
+| COMPLETE | C | The results have been copied back to the datastore. |
+
+### Worker
+
+The `Worker` column on the tasks table shows which worker is managing task execution, and which thread on that worker.
+
+At the moment, the "which worker" question is kind of dull, since there's only one worker per cluster, and the console has to run on the same computer as the worker. This is why the worker is always listed as "localhost". This may not be true in the future, so we've left this information on the display.
+
+Recall from the discussion on [Running the Cluster](running-pipeline.md) that the worker has multiple threads that can execute in parallel. The thread number tells you which of these is occupied with a given task.
+
+### P-Time
+
+Both the instances table and the tasks table have a column labeled `P-time`. This represents the total "wall time" the instance or task has been running. Put simply, `P-time` is a timer or stopwatch that starts when the task or instance starts running, stops when the instance or task completes or fails, and starts again if the task or instance restarts. It's thus the total-to-date time spent processing, including any time spent in execution attempts that failed.
diff --git a/doc/user-manual/intermediate-topics.md b/doc/user-manual/intermediate-topics.md
new file mode 100644
index 0000000..f14e4df
--- /dev/null
+++ b/doc/user-manual/intermediate-topics.md
@@ -0,0 +1,15 @@
+## Intermediate Topics
+
+Now that you've [successfully run a pipeline](start-pipeline.md), there are some other things you should look at before we move on to what happens when things don't go quite as well.
+
+### [The Datastore and the Task Directory](datastore-task-dir.md)
+
+The permanent, and temporary, storage areas for data and other files.
+
+### [The Task Configuration Parameter Sets](task-configuration.md)
+
+How Ziggy knows how to divide work up into tasks, and much, much more.
+
+### [The Properties File](properties.md)
+
+Everything you never wanted to know about how Ziggy sets paths, but were forced to find out.
diff --git a/doc/user-manual/log-files.md b/doc/user-manual/log-files.md
new file mode 100644
index 0000000..cb81464
--- /dev/null
+++ b/doc/user-manual/log-files.md
@@ -0,0 +1,174 @@
+## Log Files in Ziggy
+
+Ziggy produces a substantial number of different kinds of log files. All of them are in the logs directory under the main pipeline results directory, with one exception: the subtask algorithm log, which is in the subtask directory.
+
+Anyways, if you look at the logs directory, you'll see this:
+
+```console
+logs$ ls
+algorithms cli db manifests state worker ziggy
+logs$
+```
+
+Let us consider each of these directories in turn.
+
+### db Directory
+
+This is where the log files from the relational database application are stored. We're using Postgresql, so the main content of this directory is `pg.log`. If a system database is being used, this directory will not be present (since the sysadmin and DBA get to decide where the logs go, not you).
+
+### state Directory
+
+These aren't -- quite -- log files as such. They're actually files that are used to communicate between the algorithm processing system and the worker system (remember that the computer with the worker might not be the one trying to run the algorithm). Here's the contents of that directory:
+
+```console
+logs$ ls state
+ziggy.1.2.permuter.COMPLETE_4-4-0
+ziggy.1.3.permuter.COMPLETE_4-4-0
+ziggy.1.4.flip.COMPLETE_4-4-0
+ziggy.1.5.flip.COMPLETE_4-4-0
+ziggy.1.6.averaging.COMPLETE_1-1-0
+ziggy.1.7.averaging.COMPLETE_1-1-0
+ziggy.2.8.permuter.COMPLETE_4-4-0
+ziggy.2.9.permuter.COMPLETE_4-4-0
+ziggy.2.10.flip.COMPLETE_4-4-0
+ziggy.2.11.flip.COMPLETE_4-4-0
+ziggy.3.12.permuter.COMPLETE_4-3-1
+ziggy.3.13.permuter.COMPLETE_4-3-1
+logs$
+```
+
+Each task has a state file that the task execution system updates with the task processing state and the subtask counts for that task; by which we mean, the task execution system keeps changing the name of the file to reflect the current state of the task. The monitoring subsystem in the worker looks at these files to determine the current state of each task, which is then reflected on the GUI.
+
+In this case, we see that instance 3, task 12 has completed (i.e., the algorithm is no longer running) with final score 4 / 3 / 1, which we already knew.
+
+### cli Directory
+
+Here we see the logs that are produced by various parts of Ziggy that are started from the command line. Looking inside the directory, we see this:
+
+```console
+logs$ ls cli
+cluster.log console.log
+logs$
+```
+
+The `cluster.log` file is logging from all the cluster commands; `console.log` captures all logging from the console. You'll sometimes see another file, `runjava.log`, which is the logging from other commands executed by `runjava`. Feel free to look into these, but none of them will provide any insight to the exception we're trying to understand.
+
+### worker Directory
+
+The worker directory's contents look like this:
+
+```console
+logs$ ls worker
+metrics-dump-90188.txt metrics-dump-90188.txt.old worker.log
+logs$
+```
+
+The `worker.log` file logs everything the worker does. This is useful for troubleshooting problems that are more directly focused on misbehavior by the worker itself, which isn't our problem today.
+
+### ziggy Directory
+
+The `ziggy` directory has the log files for the "Ziggy-side" parts of task execution: specifically, marshaling of inputs and persisting of outputs. The directory looks like this:
+
+```console
+logs$ ls ziggy
+1-1-data-receipt.0-0.log
+1-4-flip.0-2.log
+1-7-averaging.0-2.log
+2-8-permuter.0-2.log
+1-2-permuter.0-0.log
+1-5-flip.0-0.log
+2-10-flip.0-0.log
+2-9-permuter.0-0.log
+1-2-permuter.0-2.log
+1-5-flip.0-2.log
+2-10-flip.0-2.log
+2-9-permuter.0-2.log
+1-3-permuter.0-0.log
+1-6-averaging.0-0.log
+2-11-flip.0-0.log
+1-3-permuter.0-2.log
+1-6-averaging.0-2.log
+2-11-flip.0-2.log
+1-4-flip.0-0.log
+1-7-averaging.0-0.log
+2-8-permuter.0-0.log
+3-12-permuter.0-0.log
+3-13-permuter.0-0.log
+logs$
+```
+
+Every task has logs with the usual nomenclature of instance number, task number, and module name separated by hyphens. Note that most tasks have 2 log files: the first ends in `-0.log`; the second, `-2.log`. The thing to understand is that the logs from task execution are numbered in order. Thus, `1-2-permuter.0-0.log` is the first log file for `1-2-permuter`, which is the marshaling log, while `1-2-permuter.0-2.log` is the third log file, for persisting.
+
+So where is `1-2-permuter.0-1.log`, and what does it cover? That's the log file for the step that comes between marshaling and persisting, which is algorithm execution, and that file is in ...
+
+### algorithms Directory
+
+Here's what the algorithm directory looks like:
+
+```console
+logs$ ls algorithms
+1-2-permuter.0-1.log
+1-4-flip.0-1.log
+1-6-averaging.0-1.log
+2-10-flip.0-1.log
+2-8-permuter.0-1.log
+1-3-permuter.0-1.log
+1-5-flip.0-1.log
+1-7-averaging.0-1.log
+2-11-flip.0-1.log
+2-9-permuter.0-1.log
+3-12-permuter.0-1.log
+3-13-permuter.0-1.log
+logs$
+```
+
+There's one log file for every task.
+
+Because execution failed during algorithm processing, not marshaling or persisting, this is likely to be the log file to use if we want insight into the problem. If you open the file `3-12-permuter.0-1.log` and swim down, a bit, sure enough you find this block:
+
+```console
+2022-09-19 19:05:03,361 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine] (st-0) Traceback (most recent call last):
+2022-09-19 19:05:03,361 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine] (st-0) File "ziggy/sample-pipeline/build/env/lib/python3
+2022-09-19 19:05:03,362 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine] (st-0) permute_color(data_file, throw exception, produce_output)
+2022-09-19 19:05:03,362 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine) (st-0) File "ziggy/sample-pipeline/build/env/lib/python3
+2022-09-19 19:05:03,362 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine] (st-0) raise ValueError("Value error raised because throw_exception is true
+2022-09-19 19:05:03,362 INFO [Exec Stream Pumper:WriterLogOutputStream.processLine] (st-0) ValueError: Value error raised because throw exception is true
+2022-09-19 19:05:03,383 WARN [pool-2-thread-1:SubtaskExecutor.execAlgorithm] (st-0) Marking subtask as failed because retCode = 1
+2022-09-19 19:05:03,385 WARN [pool-2-thread-1:SubtaskExecutor.execAlgorithm] (st-0) Marking subtask as failed because an error file exists
+2022-09-19 19:05:03,386 INFO [pool-2-thread-1:SubtaskMaster.executeSubtask] (st-0) FINISH subtask on host, rc: 1
+2022-09-19 19:05:03,386 ERROR [pool-2-thread-1: SubtaskMaster.processSubtasks] (st-0) Error occurred during subtask processing
+gov.nasa.ziggy.module.ModuleFatalProcessingException: Failed to run: permuter, retCode=1
+ at gov.nasa.ziggy.module.SubtaskMaster.executeSubtask (SubtaskMaster.java:2381 ~[ziggy.jar:?]
+ at gov.nasa.ziggy.module.SubtaskMaster.processSubtasks (SubtaskMaster.java:123) [ziggy.jar:?]
+ at gov.nasa.ziggy.module.SubtaskMaster.run(SubtaskMaster.java:79) [ziggy.jar:?]
+ at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_265]
+ at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_265]
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]
+ at java.lang.Thread run(Thread.java:749) [?:1.8.0_265]
+```
+
+There's the Python traceback, which is what Python does when an exception occurs. The text shows you exactly what was wrong in this case: permuter errored because we told it to error! Okay, so in this case it's not exactly a revelation, but under ordinary circumstances, when you don't deliberately crash your algorithm, this will be much more informative.
+
+While we're here, note the parts of the log that have `(st-0)` in them. The algorithm log covers all of the subtasks in a single log. In order to figure out which subtask goes to a particular line of logging, the subtask number is inserted into each line of logging that comes from a subtask.
+
+### Subtask Algorithm Log
+
+This is the one exception to the log files being in the logs directory. If you go to the `3-12-permuter/st-0` directory under task-data, you'll see the `permuter-stdout-0.log` file. This is a transcription of all the standard output from the permuter algorithm module. It looks like this:
+
+```console
+Traceback (most recent call last):
+ File "ziggy/sample-pipeline/build/env/lib/python3.8/site-packages/major_tom/permuter.py", line 50, in
+ permute_color(data_file, throw_exception, produce_output)
+ File "ziggy/sample-pipeline/build/env/lib/python3.8/site-packages/major_tom/major_tom.py", line 34, in permute_color
+ raise ValueError("Value error raised because throw_exception is true")
+ValueError: Value error raised because throw_exception is true
+```
+
+This is a subset of the algorithm log file, above. What's up with that?
+
+Well, the answer is: Ziggy automatically sends all the standard output from the algorithms to the appropriate `stdout-0.log` file in the subtask directory; but it also sends that same information back through the main logging system so it ends up in the task algorithm log.
+
+Why do it this way?
+
+Remember what we said earlier about subtask directories: sometimes it's helpful to copy them to a different file system on a different computer so that a subject matter expert can troubleshoot issues on the system they're familiar with (and one where they have write permissions for all the contents). By including all of the standard output from the subtask in the `stdout-0.log` file, we ensure that if the expert on the algorithm code wants to see all the output from the algorithm, it's right there for them. Even better, it's right there for them without all the "overhead" of logging messages that these experts won't be interested in (i.e., the rest of the algorithm log contents).
diff --git a/doc/user-manual/module-parameters.md b/doc/user-manual/module-parameters.md
new file mode 100644
index 0000000..32e4131
--- /dev/null
+++ b/doc/user-manual/module-parameters.md
@@ -0,0 +1,38 @@
+## Module Parameters
+
+### What Are Module Parameters, Anyway?
+
+Module parameters are a form of data used to control the pipeline: either the way that Ziggy executes the processing, or the way that the algorithms themselves function. For example, module parameters can tell an algorithm module to enable or disable a particular processing step; set convergence criteria for model fitting algorithms; set the number of data points to use in a median filter; or, really, anything of that nature.
+
+Module parameters are organized into groups known as "parameter sets." When Ziggy provides parameters to a pipeline module, it's in the form of one or more parameter sets, rather than individual parameters. There's no limit on the number of parameter sets that can be provided to a pipeline or a pipeline module, so don't feel that you need to cram a hundred parameters into one parameter set; it's probably the case that the parameters can be logically grouped into sets, and you should feel free to group them that way and then pass all the needed parameter sets to the modules that need them.
+
+Ziggy expects its parameter set XML files to start with "pl-" (for "Parameter Library"). In the case of the sample pipeline, take a look at [config/pl-sample.xml](../sample-pipeline/config/pl-sample.xml). Note that you also don't need to confine yourself to a single parameter library file; you can have as many as you like.
+
+### Execution Control Parameters
+
+Ziggy provides two flavors of pre-defined parameter sets that are used by Ziggy itself to control parts of its execution: the remote parameters, that control execution on a high-performance computing system; and the task configuration parameters, which define how Ziggy subdivides data for execution in chunks.
+
+For now, we're not going to talk further about these kinds of parameters. They'll be discussed at greater length in sections on running the pipeline. However, if you can't bear the suspense, see the following articles:
+
+[Remote Parameters](remote-parameters.md)
+
+[Task Configuration Parameters](task-configuration.md)
+
+### Parameters Used by Algorithm Modules
+
+This is more what you need to worry about as you're designing your algorithms. In `pl-sample.xml`, swim down to the last parameter set:
+
+```xml
+
+
+
+
+
+
+```
+
+Each parameter set must have a unique name, in this case "Algorithm Parameters". Each parameter must have a name that is unique within the parameter set (i.e., I could have another parameter named "dummy array parameter" in a different parameter set, but obviously I can't have 2 parameters with the same name in the same set).
+
+Each individual parameter in a set has a name, a value, and a type. The allowed types the usual ones you would expect: `boolean, byte, short, int, long, float, double, string`. The parameters can be scalar or they can be arrays: to specify an array, append `array` after the type name, as shown by the `dummy array parameter`.
+
+Note that both the parameter set name and the parameter name can have whitespace in them. However, when the parameter sets are provided to the algorithm, as shown in the article on [Configuring a Pipeline](configuring-pipeline.md), it's done in a context that doesn't allow for whitespace. As a result, the parameter set names and parameter names will have any whitespace replaced with underscore ("_") characters. In my opinion, this is a recipe for confusion and annoying software bugs: you have two different names for parameters and/or parameter sets, but they're *almost* the same, and you need to remember exactly what the difference is and which part of the system uses each of the names. Still, if you want to do this, you can, which is why we're showing you how to do it (and then telling you not to).
diff --git a/doc/user-manual/monitoring.md b/doc/user-manual/monitoring.md
new file mode 100644
index 0000000..4624e5a
--- /dev/null
+++ b/doc/user-manual/monitoring.md
@@ -0,0 +1,43 @@
+## Monitoring Tab
+
+When stuff goes wrong, it's often worthwhile to click over to the `Monitoring` tab. In the current situation, you'll see something like this:
+
+![](images/monitoring-worker.png)
+
+The first thing you should notice is that the colored boxes on the left mirror the colors of the stoplights. From this you can see that the stoplights are for pipelines (`Pi`), workers (`W`), Processes (`Pr`), and alerts (`A`).
+
+The next thing you'll notice is that if you click on the titles of the boxes on the left, the content of the panel on the right changes. This allows you to see the state of assorted Ziggy components.
+
+### Pipelines Status
+
+The `Pipelines` box, when clicked, just takes you back to the `Operations` tab. There's no better set of information for the status of running / completed pipelines than that.
+
+### Workers Status
+
+The view shown above is the `Workers` status panel. This shows what each of the worker's processing threads is doing. The colored box and the stoplight are grey, which indicate that all the workers are currently idle. When stuff's happening, the box and stoplight turn green, and the table on the worker status panel shows what each thread is doing.
+
+### Processes Status
+
+The `Processes` status panel looks like this:
+
+
+
+The panel shows the current status and helpful parameters for three processes: the worker, the database, and the messaging system. Ziggy's messaging system is Java's Remote Method Invocation (RMI). RMI is actually in the family of Remote Procedure Calls (RPCs), which are a way that one process can request that another process execute a method, function, procedure, or other program unit. In this case, RMI allows the worker (a Java program) and the console (another Java program) to communicate with one another.
+
+In our experience, it's really unusual for any of these processes to fail, but if they do then the colored bar for that process will change to yellow or red. In that case, the best place to look for further clues is the worker log.
+
+### Alerts Status
+
+Alerts are somewhat more informative for most problems, so alerts have [their own article](alerts.md).
+
+### Colors
+
+The colors on the left-hand panel match the colors of the stoplights at the top-right of the console. Each of the 4 subsystems (pipelines, workers, processes, alerts) can be one of: grey, green, yellow, red. Here's the decoder ring for the colors:
+
+| | Grey | Green | Yellow | Red |
+| --------- | ------------------- | --------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
+| Pipelines | No pipeline running | Pipeline(s) running | Pipeline in `ERRORS_RUNNING` state | Pipeline in `ERRORS_STALLED` state |
+| Workers | All workers idle | Worker(s) active | Not used | Not used |
+| Processes | Not used | All processes healthy | Process(es) in a failed state, attempting to recover | Process(es) in a failed state, recovery efforts failed |
+| Alerts | Not used | No new alerts | New warning level alert(s) | New error-level alert(s) |
+
diff --git a/doc/user-manual/pipeline-architecture.md b/doc/user-manual/pipeline-architecture.md
new file mode 100644
index 0000000..70c76ee
--- /dev/null
+++ b/doc/user-manual/pipeline-architecture.md
@@ -0,0 +1,12 @@
+## Pipeline Architecture
+
+As a general matter, it's not necessary to know much about the inner workings of Ziggy in order to use it. In the sections below we cover the things you actually do need to know.
+
+### Architecture Diagram
+
+
+
+The figure above shows Ziggy's architecture. Boxes in green (Algorithms, Pipeline Configuration, System Configuration) are the components that users need to supply or manage. Boxes in black (Datastore, Worker, User Interface) are parts of Ziggy. Boxes in red (Batch System, Cloud Computing Nodes, Local CPUs, Supercomputer, Working Directory File System) are system resources (note that you don't need to have *all* the components in red to make Ziggy work, so if you don't own a supercomputer or a batch system, don't panic). The box in purple is a relational database management system (RDBMS): Ziggy uses a Commercial Off-the-Shelf (COTS) database program (specifically, Postgresql), but the content in that database is generated by Ziggy. The blue box is mission data: this includes data from the instrument(s), instrument models (i.e., descriptions of the instruments that are needed for processing), etc. This is information that presumably flows to the pipeline at regular intervals and gets processed or reprocessed.
+
+A particular instance of the system above is called a **cluster**. A complete processing system may consist of more than one cluster: for example, the TESS processing pipeline has 4 clusters, each of which processes a different subset of TESS data. A single server can run multiple pipeline clusters, though that gets potentially messy. For now, let's stick with the assumption that on a given computer there's only one cluster in existence at a time.
+
diff --git a/doc/user-manual/pipeline-definition.md b/doc/user-manual/pipeline-definition.md
new file mode 100644
index 0000000..d6ab260
--- /dev/null
+++ b/doc/user-manual/pipeline-definition.md
@@ -0,0 +1,70 @@
+## Pipeline Definition
+
+The pipeline definition files are where all the pieces get put together. Ziggy expects these files to have names that start with "pd-" (for "Pipeline Definition"). In the case of the sample pipeline, the file is [config/pd-sample.xml](../../sample-pipeline/config/pd-sample.xml). There can be more than one pipeline definition file. These files define the pipelines themselves, but first they define the individual algorithm elements of the pipelines, the modules.
+
+### Pipeline Modules
+
+Here's the pipeline module definitions for the sample pipeline:
+
+```xml
+
+
+
+```
+
+Pretty simple. The main thing is that the name of the module is the name that Ziggy will look for when it comes time to execute the module in question. Thus, **each module must be an executable file that can run without parameters.** Put differently, when the permuter module runs, Ziggy will look for an executable named `permuter` and start it. Done and done.
+
+### Pipelines
+
+At last, the main event! The pipeline definition starts with something like this:
+
+ ``
+
+A name, a description and "root node names." The root node name(s) are the pipeline modules that are executed first when the pipeline starts. In this case, `data-receipt` is executed first. If the pipeline had called for multiple pipeline modules running in parallel at this first step, we could specify a comma-separated list of module names in `rootNodeNames`.
+
+#### Wait, What? Data Receipt?
+
+Notice that I've now told you 2 things that contradict each other. On the one hand, the user defines the pipeline modules with `module` elements, and the root node of a pipeline is a pipeline module. On the other hand, this pipeline's root node is `data-receipt`, which isn't a defined node! What up with that?
+
+What up is the following:
+
+Data receipt is a special case. Data receipt is a pre-defined module that takes data and instrument model files from some outside directory and transfers them to the datastore. This is the way that raw mission data and models get into the datastore in the first place. This is a sufficiently important, and generic, capability that Ziggy "ships" with a data receipt pipeline module built in. The user doesn't need to define that module; you can simply use it.
+
+Note that data receipt is the only pre-defined module in Ziggy. There's more information on how it works in the article on [Data Receipt Execution Flow](data-receipt.md).
+
+#### Back to the Pipeline Definition
+
+The next chunk of the pipeline definition is thus:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Each step in the pipeline is a node. The `node` specifies the name of the module for that node and the name of any nodes that execute next, as `childNodeNames`. Here we see the `data-receipt` node is followed by `permuter`, and `permuter` is followed by `flip`.
+
+##### Parameter Sets
+
+Parameter sets can be supplied for either the entire pipeline as a whole, or else for individual nodes.
+
+In the text above we see a `pipelineParameter` named `Algorithm Parameters`. This means that the `Algorithm Parameters` set from `pl-sample.xml` will be provided to each and every module when it starts to execute. By contrast, the `Data receipt configuration` parameter set is provided as a `moduleParameter` to the `data-receipt` node. This means that the data receipt module will get the `Data receipt configuration` parameter set provided, but the permuter module will not.
+
+A given parameter set can be provided as a `moduleParameter` to any number of nodes. For example, the `Multiple subtask configuration` parameter set is provided as a `moduleParameter` to `permuter` and also to `flip` (not shown above, but it's in the XML file, check it out). This provides fairly fine-grained control in terms of which parameter sets go to which nodes.
+
+##### Data File and Model Types
+
+Each node can have `inputDataFileType`, `outputDataFileType`, and `modelType` elements. This is how the user defines what file types are used for inputs, which for outputs, and which models are needed for each node. Here we see that the `raw data` type is used as input for `data-receipt` and for `permuter`. The `permuted colors` type is the output type for permuter, and it uses the `dummy model` model type. Thus: we have now defined the names of the files that will be used as the input to the permuter node, and the ones that will be produced as output.
+
+A node can have multiple output types (see for example the `flip` node in `pd-sample.xml`) or multiple input types (as in the `averaging` node). Each node can use any combination of model types it requires, and each model type can be provided to as many nodes as need it.
diff --git a/doc/user-manual/properties.md b/doc/user-manual/properties.md
new file mode 100644
index 0000000..5fc2c56
--- /dev/null
+++ b/doc/user-manual/properties.md
@@ -0,0 +1,127 @@
+## The Properties File
+
+When we discussed [configuring the pipeline](configuring-pipeline.md), we mentioned the properties file, which provides Ziggy with all of its global configuration. Let's get into detail on this now.
+
+### Syntax
+
+The properties file is a flat text file that consists of name-value pairs separated by an equals sign (=). Both the name and the value are text strings.
+
+#### Property Expansion
+
+Properties can be defined in terms of the values of other properties by use of the `${...}` syntax. For example, in the [sample.properties](../../sample-pipeline/etc/sample.properties) file, we see these definition:
+
+```
+pipeline.results.dir = ${pipeline.home.dir}/pipeline-results
+```
+
+When Ziggy needs the value of `pipeline.results.dir`, it first finds the value of `build.dir`, and then appends `/pipeline-results` onto the end of that string. Note that for some reason this is sometimes referred to as "interpolation."
+
+#### Use of Environment Variables
+
+The properties manager can also reach out to the environment and pull in the value of an environment variable for use in a property. This is done with the `${env:...}` syntax. For example, the sample properties file defines the `ziggy.root` property in terms of the `ZIGGY_ROOT` environment variable:
+
+```
+hibernate.connection.username = ${env:USER}
+```
+
+### Pipeline Properties vs Ziggy Properties
+
+Ziggy actually uses two properties files.
+
+The pipeline properties file contains the properties that you, the pipeline user, are more likely to want to edit. This is the file that `PIPELINE_CONFIG_PATH` needs to point to.
+
+The other properties file is the Ziggy properties file, which is stored with the main Ziggy code, at `etc/ziggy.properties`. These are properties that you, the pipeline user, are unlikely to ever want to mess with. Ziggy finds the latter via -- yeah, you got it -- a property in the former, specifically `ziggy.config.path`.
+
+In a real, normal pipeline, our recommendation is to put the pipeline properties file someplace outside of the directories that are under version control. This way you can modify them to your heart's content without fear that you'll accidentally commit your changes back to the Ziggy repository. The Ziggy properties file can stay in the version-controlled directories unless you decide to modify it, in which case it, too, needs to be copied out to avoid corrupting the repository.
+
+### The Properties
+
+Without further ado, here's the list of properties that Ziggy gets from the properties files. Note that these are the properties that Ziggy itself uses. You can define any other properties you like, and define the ones in this section in terms of the new ones you've defined, if that makes your properties file easier to use or maintain.
+
+#### General Path-Setting
+
+| Property Name | Description | File |
+| ----------------------- | ------------------------------------------------------------ | -------- |
+| pipeline.home.dir | Top-level directory for the pipeline code. | Pipeline |
+| pipeline.results.dir | Location for working directories, log files, etc. | Pipeline |
+| pipeline.definition.dir | Location for XML files that define the pipeline | Pipeline |
+| pipeline.classpath | Java classpath for pipeline-side Java classes | Pipeline |
+| pipeline.logoFile | Location and name of the image logo file to be used as an icon (supported formats: PNG, JPEG, BMP, GIF, WBMP) | Pipeline |
+| data.receipt.dir | Directory used by data receipt | Pipeline |
+| database.dir | Directory used by RDBMS; if empty, the system database is used | Pipeline |
+| datastore.root.dir | Root directory for datastore | Pipeline |
+
+#### Paths to Ziggy
+
+| Property Name | Description | File |
+| ----------------- | ------------------------------------------- | -------- |
+| ziggy.home.dir | Location of the `build` directory for Ziggy | Pipeline |
+| ziggy.config.path | Location of Ziggy's configuration file | Pipeline |
+
+#### Algorithm Module Paths
+
+In order to avoid conflicts with the rest of the user's environment configuration, when Ziggy runs an algorithm module it creates an environment in which the executable and library paths are replaced by values specified in the pipeline configuration file. This means that the user isn't obligated to put any pipeline-related paths into their `PATH` or `LD_LIBRARY_PATH` environment variables.
+
+| Property Name | Description | File |
+| --------------------------- | ------------------------------------------------------------ | -------- |
+| pi.worker.moduleExe.binPath | Colon-separated list of directories to search for algorithm executables | Pipeline |
+| pi.worker.moduleExe.libPath | Colon-separated list of directories to search for shared libraries (i.e., files with .so or .dylib suffix) | Pipeline |
+
+#### Algorithm Environment
+
+| Property Name | Description | File |
+| ------------------------ | ------------------------------------------------------------ | -------- |
+| pi.moduleExe.environment | Comma-separated list of name-value pairs of environment variables that should be provided to the algorithm at runtime. | Pipeline |
+
+#### Database Properties
+
+| Property Name | Description | File |
+| ---------------------- | ------------------------------------------------------------ | -------- |
+| database.software.name | The flavor of database in use, also used in the database URI. Currently, only postgresql is supported. | Pipeline |
+| database.name | Name of the database (you're going to be stuck with this forever once you pick it, so choose wisely) | Pipeline |
+| database.port | Connection port for RDBMS. Each cluster on a given system must have its own database port. More generally, each cluster must have a port that is not already in use on the system, so you need to avoid trying to use a port that some other joker has already taken. | Pipeline |
+| database.host | Hostname for RDBMS (usually localhost) | Ziggy |
+| database.connections | Number of connections database will accept. Might not be used if the database is a system database. | Pipeline |
+
+#### Worker Resources
+
+| Property Name | Description | File |
+| --------------------- | ------------------------------------------------------------ | -------- |
+| rmi.registry.port | Port used for connections between worker and UI. Same conditions as for the database port (i.e., each cluster must have a port that's unique and not in use by some other joker). | Pipeline |
+| pi.worker.heapSize | Size of Java heap for worker process, in MB (can be overridden by the `--workerHeapSize` option in `runjava cluster start`) | Pipeline |
+| pi.worker.threadCount | Number of worker threads (can be overridden by the `--workerThreadCount` option in `runjava cluster start`); set to zero to have 1 thread per CPU "core" | Pipeline |
+
+#### Remote Execution Properties
+
+| Property Name | Description | File |
+| ------------------- | ------------------------------------------------------------ | -------- |
+| remote.user | Username to be used when submitting jobs to batch system | Pipeline |
+| remote.group | Group ID to be used when submitting jobs to batch system | Pipeline |
+| remote.cluster.name | Flavor of remote system used. Supported values are "NAS" (i.e., the HPC facility at NASA Ames Research Center), "AWS" (i.e., Amazon Web Services). | Pipeline |
+
+#### Behavior Properties
+
+| Property Name | Description | File |
+| ------------------------------- | ------------------------------------------------------------ | -------- |
+| moduleExe.useSymlinks | Use symbolic links rather than copies when staging files to working directory | Pipeline |
+| moduleExe.memdrone.enabled | Enable/disable memory consumption tracker | Pipeline |
+| moduleExe.memdrone.sleepSeconds | Sample interval for memory consumption tracker | Pipeline |
+| pi.processing.halt.step | Automatically halt pipeline after a given processing step (marshaling, submitting, etc.). Mainly for debugging. See the article on [The Instances Panel](instances-panel.md) for more about processing steps. | Pipeline |
+
+#### Hibernate Properties
+
+These properties control the behavior of Hibernate, the API that links Ziggy to the database.
+
+| Property Name | Description | File |
+| ----------------------------------- | ------------------------------------------------------------ | -------- |
+| hibernate.connection.username | Username for use when connecting to database | Pipeline |
+| hibernate.show_sql | Write generated queries to log file | Ziggy |
+| hibernate.format_sql | "Pretty-print" generated queries written to log file | Ziggy |
+| hibernate.use_sql_comments | Generate comments in generated queries | Ziggy |
+| hibernate.jdbc.batch_size | Size of batches used in updates | Ziggy |
+| hibernate.id.new_generator_mappings | Allow sequence generators to have initial values | Ziggy |
+| hibernate.connection.url | URL for database connections (default: jdbc:${database.software.name}://${database.host}:${database.port}/${database.name}) | Ziggy |
+| hibernate.connection.password | Password for database connections | Ziggy |
+| hibernate.controller.driver_class | Java class used by Hibernate to manage connections. Only needed if using a database application not supported by Ziggy. | Ziggy |
+| hibernate.dialect | Database dialect used by Hibernate. Only needed if using a database application not supported by Ziggy. | Ziggy |
+
diff --git a/doc/user-manual/rdbms.md b/doc/user-manual/rdbms.md
new file mode 100644
index 0000000..9b3cc8a
--- /dev/null
+++ b/doc/user-manual/rdbms.md
@@ -0,0 +1,110 @@
+## Setting up the Relational Database Management System (RDBMS)
+
+Ziggy uses a commercial off-the-shelf (COTS) database application to keep track of a lot of the details of pipeline operations and generally to provide permanent records of lots and lots of information. For this purpose, pretty much any RDBMS will suffice, but Ziggy "ships" with support for Postgresql. Postgresql is free and open-source, has great support and documentation, and performs well.
+
+That said, we found it impossible to integrate "download and build" support for Postgresql into the Ziggy build system. We're also aware that you may not want to download and build Postgresql yourself. You may want to use a Postgresql database that is set up and maintained by your system administrator (the "system database"). You may want to use a system database that's not Postgresql at all. For this reason, this article will walk you through three options: using your own copy of Postgresql; using the system Postgresql database; and using a non-Postgresql database, either one that you create and run or one that your sysadmin sets up on your system database.
+
+### Your own private Postgresql
+
+This is in many ways the easiest solution, especially if all you really want to do at this point is set up Ziggy, see that it runs, and get a sense for the features. It's easiest in that it doesn't require a sysadmin to set anything up for you, and it's integrated into the rest of Ziggy's systems.
+
+Anyway, the steps look like this:
+
+#### Install Postgresql
+
+On a Macbook you can use Homebrew or Macports to install Postgresql in your home directory, assuming you have the permissions for that. On Linux you can use the system's package manager if you have permissions for that. Users of any OS can go to [the Postgresql website](https://www.postgresql.org/download/) and download executables or source code. We've historically used the now-somewhat-outdated Postgresql 10, but testing indicates that any version up to 14 will work. Not surprising, as our demands on the database are relatively simple.
+
+#### Put the Postgresql Executables on your Search Path
+
+In other words, "make sure that the postgresql bin directory is part of your PATH environment variable." Alternately, you can fill in `database.bin.dir` in [the properties file](properties.md).
+
+#### That's it.
+
+With the steps above done, you should be ready to [configure your pipeline](configuring-pipeline.md).
+
+### System Postgresql
+
+This case is a bit more involved for the database and/or system administrators. On the other hand, it's less work for the user in that it delegates installation, care and feeding of the database application to the system administrators. Anyway:
+
+#### Create the Database
+
+The system database is a system-level instance of Postgresql that can run multiple relational databases simultaneously. This means that Postgresql needs to be installed by your system administrator, if it isn't already installed, and your Ziggy database needs to be created for you by the database administrator (DBA).
+
+#### Update the Properties File
+
+In order to use the system Postgresql executable, you'll need to set some values in [the properties file](properties.md). Specifically:
+
+- The property `database.port` needs to be set to whatever port the system database uses for connections. The value in `pipeline.properties.EXAMPLE` is the default Postgresql port (5432), so you might not need to change it.
+- The property `database.dir` needs to be removed. This tells Ziggy where it should put the files that are the content of the database, and in this case you don't need that because Ziggy isn't making that decision, your sysadmin is.
+- The property `database.conf.file` needs to be removed. This tells Postgresql how to configure itself; again, in this case, your sysadmin has made all those decisions for you.
+- Other database properties need to be set according to instructions from your system administrator.
+
+#### One Last Thing
+
+If Ziggy's database runs in the context of the system's RDBMS, then Ziggy won't attempt to start or stop the database when it starts and stops the rest of its systems (again, see [the article on running the cluster](running-pipeline.md)). In essence, the database is "running" all the time.
+
+However, this also means that if the database goes down (for example when the system is shut down), it may need a system administrator to bring it back to life.
+
+### Non-Postgresql RDBMS
+
+This is the most complex option to use, and I recommend that you not go this route right from the start. Instead, try using a private Postgresql database while you're trying out Ziggy and figuring out whether you ever want to do anything further with it! Once you've made that call, then it might be time to think about which RDBMS you're going to use.
+
+In case you've already done the above and definitely want to run Ziggy, but definitely don't want to use Postgresql; or if creating any kind of Postgresql database is out of the question for you for whatever reason (usually privilege issues), read on.
+
+The best way to accomplish this is to contact the Ziggy team and ask us whether we'd be willing and able to add support for RDBMS applications other than Postgresql.
+
+The main reason we only support Postgresql is that we haven't had any requests yet to add another RDBMS, and in the absence of such a request we wouldn't know which ones to add and don't want to needlessly make work for ourselves. That said, in principle Ziggy is written in a way that should make it straightforward to add support for additional databases. If somebody actually wants to use one, then by definition it's not needless work, so we'll do it if we can!
+
+In the event of impatience or bravery on your part, you can make a different RDBMS work for you without any help from us. Here's what you need to do:
+
+#### Create the Database
+
+Either you (in the case of a private database) or your DBA (in the case of a system database) has to create a database for your use.
+
+#### Get the Appropriate Database JAR File
+
+A database JAR file is the glue that connects Ziggy's Java code to the database application. Ziggy downloads the JAR files for Postgresql and HSQLDB, but no other RDBMS applications. For other applications, you'll need to get the JAR file.
+
+In order to tell Ziggy where to find the JAR file, you'll want to put the file system path to the JAR file into the `pipeline.classpath` property of the properties file. Alternately, you could put it into `ziggy/build/libs`, but if you do that then whenever you run `./gradlew clean`, the JAR file will get deleted and won't get replaced.
+
+This JAR file will contain the database driver class definition; you'll need that classname for the next couple of steps.
+
+#### Generate The Database Schema
+
+Ziggy generates the schema for its database tables in Postgresql format (and also HSQLDB, but never mind that for now). This may or may not work for some other kind of database. You can generate the tables for some other database using a utility that Ziggy provides:
+
+```console
+$ runjava -Dhibernate.connection.driver_class= -Dhibernate.dialect= gov.nasa.ziggy.services.database.ZiggySchemaExport --create --output=
+```
+
+You'll need to substitute in the name of the driver class for ``, the name of the dialect for `` , and the output file name and location for ``. For the dialect, use the fully-qualified name of one of the classes in the `org.hibernate.dialect` package as a string.
+
+#### Create the Database Tables
+
+Using the files created in the previous step, create the necessary tables in the database. There will be some command you can use to read in the files and generate the necessary tables and cross-references.
+
+#### Update the Properties File
+
+You'll need to make several changes:
+
+- The property `database.port` needs to be set to whatever port the database uses for connections.
+- The property `database.sofware.name` must be removed.
+- The properties `hibernate.dialect` and `hibernate.connection.driver_class` need to be set to the values you used a couple steps ago when generating the schema.
+
+#### Populate the Database Tables
+
+This amounts to manually importing into the database the XML files that define the pipeline, parameters, and data types. Fortunately, there are runjava commands you can use for all of these actions:
+
+- The command `runjava pl-import` allows you to read in the parameter library files.
+- The command `runjava pt-import` allows you to read in the data file type definitions.
+- The command `runjava pd-import` allows you to read in the pipeline definitions.
+
+All of the commands listed above will allow you to get help to know the exact syntax, order of arguments, etc. For more information on runjava, take a look at [the article on running the cluster](running-pipeline.md). Most importantly: **Be sure to run the commands in the order shown above**, and specifically **be sure to run pd-import last!**
+
+#### One Last Thing
+
+As with a Postgresql system database, a database that uses some other RDBMS application will be controlled by you, or your system administrator, and not Ziggy. Ziggy will not attempt to stop or start the database.
+
+### Okay, What Next?
+
+Now we move on to running a [pipeline in a cluster!](running-pipeline.md)
diff --git a/doc/user-manual/remote-dialog.md b/doc/user-manual/remote-dialog.md
new file mode 100644
index 0000000..5534bfb
--- /dev/null
+++ b/doc/user-manual/remote-dialog.md
@@ -0,0 +1,73 @@
+## The Remote Execution Dialog Box
+
+When you submit a job request to the batch system for an HPC facility, it typically wants you to specify your request in "machine-centric" units: how many compute nodes, of what kind, for how long? Meanwhile, what you know is something about the resources that each subtask will likely need (RAM and CPU hours), you may or may not know how many subtasks there are, and you probably care about things like, "How long will this take?", "How much will it cost?", and "How long will my jobs be stuck in the queue?"
+
+Ziggy provides a tool that can get you from what you know and/or care about to what PBS needs.
+
+### Opening the Remote Execution Dialog Box
+
+Go to the `Triggers` tab and double-click on the sample pipeline row in the table. You'll see this:
+
+
+
+A whole new dialog box we've never seen before! But actually we're just going to use it to get to yet another one. Select `permuter` from the modules list and press `Remote Execution`. You'll see this:
+
+![](images/remote-dialog-1.png)
+
+### Using the Remote Parameters Dialog Box
+
+Notice that the values you've set in the `RemoteParameters` instance for permuter have been populated, as has the total number of subtasks that Ziggy found for this node, based on the contents of the datastore. In the interest of making this more realistic, change the number in `Total subtasks` to 1000, then hit the `Calculate Pbs Parameters` button. You'll see this:
+
+![](images/remote-dialog-2.png)
+
+The parameters that will be used in the request to PBS are shown in the `PBS Parameters` box. Ziggy will ask for 84 nodes of the Haswell type, for 15 minutes each; the total cost in Standard Billing Units (SBUs) will be 16.8.
+
+A Haswell node at the NAS has 24 cores and 128 GB of RAM. Since we've asserted that each subtask takes 10 GB, then a maximum of 12 subtasks will run in parallel on each node, and thus there will be 12 active cores per node (and 12 idled).
+
+What did Ziggy actually do here? Given the parameters we supplied, Ziggy looked for the architecture that would minimize the cost in SBUs, which turns out to be Haswell, and it asked for enough nodes that all of the subtasks could execute in parallel. This latter minimizes the estimated wall time, but at the expense of asking for a lot of nodes.
+
+#### Setting the Maximum Number of Nodes
+
+Given the above, it might be smarter to ask for fewer nodes. If we change the `Max Nodes` value to 10 and press `Calculate PBS Parameters`, this is what we see:
+
+![](images/remote-dialog-3.png)
+
+As expected, the number of remote nodes went down and the wall time went up. What's unexpected is that the total cost also went down! What happened?
+
+This is due to the confluence of two factors. First, Ziggy always rounds its wall time requests up to the nearest quarter-hour. Second, Ziggy doesn't use the max nodes as a parameter it tries to adjust to minimize the cost.
+
+In the first example, Each node was really going to be needed for 0.15 hours, based on the wall time estimates we provided. Because of the round-up, it asked for them for 0.25 hours each. Thus the request was asking for an extra 0.1 hours per node; multiply by 84 nodes and it starts to add up.
+
+In the second example, given the parameters requested, the actual wall time needed would be -- 1.25 hours, so exactly what Ziggy requested (i.e., the round-up is miniscule). Thus the second example has a lower total cost.
+
+That said: Once the HPC has processed all the subtasks, the jobs all exit and the nodes are returned to the HPC pool. The user is only charged for the actual usage. In the first case, what would have happened is that all the jobs would finish early, and we'd only get billed for what we actually used, which would be more like 10 SBUs than 17 SBUs.
+
+#### Selecting a Different Optimizer
+
+Right now the `Optimizer` is set to `COST`, meaning that Ziggy will attempt, within the limits of its smarts, to find the compute node architecture that minimizes the cost in SBUs. There are 3 other settings available for the optimizer: `CORES`, `QUEUE_DEPTH`, and `QUEUE_TIME`. These are described in detail in the article on [Remote Parameters](remote-parameters.md).
+
+#### Manually Selecting an Architecture
+
+The `Architecture` pull-down menu allows you to manually select an architecture for the compute nodes. This in turn allows you to run the wall time and SBU calculation and see the results for each architecture.
+
+#### Enabling or Disabling Node Sharing
+
+So far we've implicitly assumed that each compute node can run subtasks in parallel up to the limit of the number of active cores per node. That is to say, in this example a Haswell node will always have 12 permuter subtasks running simultaneously. This is the situation we call "Node Sharing."
+
+There are cases when this won't be true -- when it won't be safe to try to force a node to run many subtasks in parallel. In those cases, you can uncheck the Node Sharing check box and run the PBS parameter calculation. You get this:
+
+![](images/remote-dialog-4.png)
+
+Ziggy no longer asks about GB per subtask, because only 1 subtask will run at a time on each node, so there's an assumption that the available RAM on any architecture will be sufficient. The cost has ballooned, which is expected since now each node can only process 1 subtask at a time. Somewhat unexpectedly, the optimal architecture has changed. This is because, with each node processing 1 subtask at a time, the benefits to having a lot of cores in a compute node go away and architectures with fewer cores are potentially favored.
+
+Now: hopefully, the reason you're disabling node sharing is because your algorithm program has its own, internal concurrency support, and that support spreads the work of the subtask onto all the available cores on the compute node. When this is the case, check the `Wall Time Scaling` box. When this box is checked, Ziggy assumes that the wall times provided by the user are wall times for processing on 1 core, and that the actual time will go inversely with the number of cores (i.e., the parallelization benefit is perfect). When you do this and calculate the parameters, you see this:
+
+![](images/remote-dialog-5.png)
+
+This results in a cost even lower than what we saw before! This is because, in this configuration, Ziggy assumes that every core can be utilized regardless of how much RAM per core the architecture has.
+
+### Keeping or Discarding Changes
+
+After some amount of fiddling around, you may reach a configuration that you like, and you'd like to ensure that Ziggy uses that configuration when it actually submits your jobs. To make that happen, press the `Save to DB` button. Ziggy will update the `RemoteParameters` instance that goes with this node according to your settings.
+
+Alternately, you may want to discard all your changes and start over tuning the remote configuration from the original values. To do this, select `Reset to DB Values`.
diff --git a/doc/user-manual/remote-parameters.md b/doc/user-manual/remote-parameters.md
new file mode 100644
index 0000000..e927dce
--- /dev/null
+++ b/doc/user-manual/remote-parameters.md
@@ -0,0 +1,144 @@
+## Remote Parameters
+
+The way that you set up a pipeline module to run on a remote (i.e., high-performance computing / cloud computing) system is to create a `ParameterSet` of the `RemoteParameters` class, and then make it a module parameter set for the desired node.
+
+Wow! What does all that mean? Let's start with the second half: "make it a module parameter set for the desired node." If you look at `pd-sample.xml`, you'll see this:
+
+```xml
+
+
+
+
+
+
+
+```
+
+That line that says ` `tells Ziggy that there's a parameter set with the name `Remote Parameters (permute color)` that it should connect to tasks that run the `permuter` module in the sample pipeline.
+
+Now consider the first part of the sentence: "create a `ParameterSet` of the `RemoteParameters` class." If you look at pl-sample.xml, you'll see this:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+This is the parameter set that, we saw above, got attached to the `permuter` node.
+
+Let's go back to that sentence: all we need to do to run on a supercomputer is to have one of these parameter sets in the parameter library file, and tell the relevant node to use it. Is that true?
+
+Yes. Pretty much. But as always, the deity is in the details.
+
+### RemoteParameters class
+
+Unlike the parameter sets you'll want to construct for use by algorithms, the parameter set above is supported by a Java class in Ziggy: `gov.nasa.ziggy.module.remote.RemoteParameters`. This means you'll need to include that XML attribute for any parameter set that you want to use to control remote execution, and that you can't make up your own parameters for the parameter set; you'll need to stick to the ones that the Java class defines (but on the other hand you won't need to specify the data types, since the definition of `RemoteParameters` does that for you).
+
+The `RemoteParameters` class has a lot of parameters, but there are only four that you, personally, must set. These can be set either in the parameter library file or via the [module parameters editor](change-param-values.md) on the console. The remainder can, in principle, be calculated on your behalf when Ziggy goes to submit your jobs via the Portable Batch System (PBS). In practice, it may be the case that you don't like the values that Ziggy selects when left to its own devices. For this reason, you can specify your own values for the optional parameters. Ziggy will still calculate values for the optional parameters you leave blank. In this case, Ziggy's calculated parameters will always (a) result in parameters that provide sufficient compute resources to run the job, while (b) taking into account the values you have specified for any of the optional parameters.
+
+Note that, rather than setting optional parameters via the module parameters editor, there's an entire separate system in Ziggy that allows you to try out parameter values, see what Ziggy calculates for the remaining optional parameters, and make changes until you are satisfied with the result. This is the [remote execution dialog](remote-dialog.md).
+
+Anyway, let's talk now about all those parameters.
+
+#### Required Parameters
+
+##### enabled (boolean)
+
+The `enabled` parameter does what it sounds like: if `enabled` is true, the node will use remote execution; if it's false, it will run locally (i.e., on the same system where the worker process runs).
+
+Note that, since you can edit the parameters, you can use this parameter to decide at runtime whether to run locally or remotely.
+
+##### minSubtasksForRemoteExecution (int)
+
+One thing about remote execution is that you may not know whether you want remote execution when you're about to submit the task. How can that happen? The main way is that you might not know at that time how many subtasks need to run! You may be in a situation where if there's only a few subtasks you'd rather run locally, but if there are more you'll use HPC.
+
+Rather than force you to figure out the number of subtasks and manually select or deselect `enabled`, Ziggy provides a way to override the `enabled` parameter and force it to execute locally if the number of subtasks is small enough. This is controlled by the `minSubtasksForRemoteExecution` parameter. If remote execution is enabled, Ziggy will determine the number of subtasks that need to be run in the task. If the number is smaller than `minSubtasksForRemoteExecution`, Ziggy will execute the task locally.
+
+The default value for this parameter is zero, meaning that Ziggy will always run remotely regardless of how many or how few subtasks need to be processed.
+
+##### subtaskTypicalWallTimeHours and subtaskMaxWallTimeHours (float)
+
+"Wall Time" is a $10 word meaning, "Actual time, as measured by a clock on the wall." This term is used to distinguish it from values like "CPU time" (which is wall time multiplied by the number of CPUs), or other compute concepts that refer to time in some way. Compute nodes in HPC systems are typically reserved in units of wall time, rather than CPU time or any other parameter.
+
+When Ziggy is computing the total resources needed for a particular task, it needs to know the wall time that a single, typical subtask would need to run from start to finish. With that information, and the total number of subtasks, it can figure out how many compute nodes will be needed, and how much wall time is needed for each compute node. The typical subtask start-to-finish time is specified as the `subtaskTypicalWallTimeHours`.
+
+That said: for some algorithms and some data, there will be subtasks that take much longer than the typical subtask to run from start to finish. Consider, for example, an algorithm that can process the typical subtask in 1 hour, but needs 10 hours for some small number of subtasks. If you ignore the handful of tasks that need 10 hours, you (or Ziggy) might be tempted to ask for a large number of compute nodes, with 1 hour of wall time for each. If you do this, the subtasks that need 10 hours won't finish. Your requested compute resources need to take into account those long-running subtasks. (This is sometimes analogized as, "If the brownie recipe says bake at 350 degrees for 1 hour, you can't bake at 700 degrees for 30 minutes instead." In this case, a more accurate analogy would be, "You can't split the brownie batter into 2 batches and bake each batch at 350 degrees for 30 minutes.")
+
+To address this, you specify the `subtaskMaxWallTimeHours`. Ziggy guarantees that it won't ask for a wall time less than this value, which should (fingers crossed!) ensure that all subtasks finish.
+
+One thing about this: Ziggy will ask for a wall time that's sufficient for the `subtaskMaxWallTimeHours` parameter, but it will ask for a total number of CPU hours that's determined by the subtask count and the `subtaskTypicalWallTimeHours`, under the assumption that the number of long-running subtasks is small compared to the number of typical ones. Imagine for example that you have 1000 subtasks, with a typical wall time of 1 hour and a max wall time of 10 hours; and you're trying to run on a system where the compute nodes have 10 CPUs each. The typical usage would be satisfied by getting 100 compute nodes for 1 hour each, but that leaves the long-running subtasks high and dry. The `subtaskMaxWallTimeHours` tells Ziggy that it needs to ask for 10 hour wall times for this task; thus it will ask for 10 hour wall times, but only 10 compute nodes total.
+
+##### gigsPerSubtask (float)
+
+This parameter tells Ziggy how many GB each subtask will need at its peak.
+
+The reason this needs to be specified is that the compute nodes on your typical HPC facility have some number of CPUs and some amount of RAM per compute node. By default, Ziggy would like to utilize all the cores on all the compute nodes it requests. Unfortunately, it's not guaranteed that each subtask can get by with an amount of RAM given by node total RAM / node total CPUs; if the subtasks need more than this amount of RAM, then running subtasks on all the CPUs of a compute node simultaneously will run out of RAM. Which is bad.
+
+The specification of `gigsPerSubtask` allows Ziggy to figure out the maximum number of CPUs on each compute node that can run simultaneously, which in turn ensures that it asks for enough compute nodes when taking into account that it may not be possible to run all the cores on the nodes simultaneously.
+
+#### Optional Parameters
+
+##### optimizer (String)
+
+Typical HPC systems and cloud computing facilities have a variety of compute nodes. The different flavors of compute nodes will have different numbers of cores, different amounts of RAM, different costs for use, and different levels of demand (translating to different wait times to get nodes). Ziggy will use the information about these parameters to select a node architecture.
+
+There are four optimizers that Ziggy allows you to use:
+
+**COST:** This is the default optimization. Ziggy looks for the node that will result in the lowest cost, taking into account the different per-node costs and different capabilities of the different node architectures (because the cheapest node architecture on a per-node basis might lead to a solution that needs more nodes or more hours, so the "cheapest" node may not result in the cheapest jobs).
+
+**CORES:** This attempts to minimize the fraction of CPUs left idled. If the subtasks need a lot of RAM, it will optimize for nodes with more RAM per CPU. If there are multiple architectures that have the same idled core fraction (for example, if all of the architectures can run 100% of their nodes), then the lowest-cost solution from the set of "semifinalist" architectures will be picked.
+
+**QUEUE_DEPTH:** This is one of the optimizers that tries to minimize the time spent waiting in the queue. The issue here is that some architectures are in greater demand than others. The `QUEUE_DEPTH` optimiztaion looks at each architecture's queued jobs and calculates the time it would take to run all of them. The architecture that has the shortest time based on this metric wins.
+
+**QUEUE_TIME:** This is a different optimization related to queues, but in this case it attempts to minimize the total time you spend waiting for results (the time in queue plus the time spent running the jobs). This looks at each architecture and computes the amount of queue time "overhead" that typical jobs are seeing. The architecture that produces the shortest total time (queue time plus execution time) wins.
+
+###### A Note About Queue Optimizations
+
+The optimization options for queue depth and queue time are only approximate, and can potentially wind up being very wrong. This is because the queue management is sufficiently complicated that the current estimates are only modestly reliable predictors of performance.
+
+What makes the management complicated? For one thing, the fact that new jobs are always being submitted to the queues, and there's no way to predict what gets submitted between now and when your job runs. Depending on the number, size, and priority of jobs that come in before your job runs, these might move ahead of your jobs in the queue. Relatedly, if a user decides to delete a job that's in the queue ahead of you, that represents an unpredictable occurrence that improves your waiting time. Jobs that don't take as long as their full wall time are another unpredictable effect.
+
+Anyway. The point is, caveat emptor.
+
+##### remoteNodeArchitecture (String)
+
+All of the foregoing is about selecting an architecture from the assorted ones that are available on your friendly neighborhood HPC facility. However: it may be the case that this isn't really a free parameter for you! For example: if you have compute nodes reserved, you probably have nodes with a specific architecture reserved. If for this reason, or any other, you want to specify an architecture, use this parameter.
+
+##### subtasksPerCore (float)
+
+Ziggy will generally gravitate to a solution in which all the subtasks in a task run simultaneously, which means it will ask for a lot of compute nodes. This parameter allows the user to force Ziggy to a solution that has fewer nodes but for more wall time. A `subtasksPerCore` of 1.0 means all the subtasks run in parallel all at the same time. A value of 2.0 means that 50% of the tasks will run in parallel and the remainder will wait for tasks in the first "wave" to finish before they can run.
+
+##### maxNodes (int)
+
+This is a more direct way to force Ziggy to a solution with a smaller number of nodes than it would ordinarily request. When this is set, Ziggy will not pursue any solution that uses more compute nodes than the `maxNodes` value. This will of course result in longer wall times than if you just ask for as many nodes as it takes to finish as fast as possible, but asking for thousands of nodes for 30 seconds each may get you talked about in the control room, and not in a good way.
+
+Note that Ziggy can request a number of nodes that is smaller than the value for `maxNodes` (which is why it's called `maxNodes` in the first place). This happens if the number of subtasks is small: if you only have 8 subtasks, and `maxNodes` is set to 30, it would clearly be useless to actually ask for 30 nodes, since most of them will be idle but you'll get charged for them anyway. In these sorts of situations (where even the value of `maxNodes` is too large, given the number of subtasks to process), Ziggy will select a number of nodes that ensures that none of the nodes sits idle.
+
+Note that Ziggy can request a number of nodes that is smaller than the value for `maxNodes` (which is why it's called `maxNodes` in the first place). This happens if the number of subtasks is small: if you only have 8 subtasks, and `maxNodes` is set to 30, it would clearly be useless to actually ask for 30 nodes, since most of them will be idle but you'll get charged for them anyway. In these sorts of situations (where even the value of `maxNodes` is too large, given the number of subtasks to process), Ziggy will select a number of nodes that ensures that none of the nodes sits idle.
+
+##### nodeSharing (boolean)
+
+In all the foregoing, we've assumed that the algorithm will permit multiple subtasks to run in parallel on a given compute node (albeit using different CPUs). In some cases, this proves to be untrue! For example, there are algorithms that have their own, internal concurrency support, but they rely on a single process having the use of all the CPUs. If this is the case for your algorithm, then set `nodeSharing` to `false`. This will tell Ziggy that each compute node can only process one subtask at a time, and that it should book nodes and wall times accordingly.
+
+##### wallTimeScaling (boolean)
+
+Related to the above: if your algorithm can use all the CPUs on a compute node, it's obviously going to run faster on compute nodes with more CPUs than on nodes with fewer CPUs. But this leads to a problem: how can Ziggy select the correct parameters when the actual run time depends on number of cores, which depends on architecture?
+
+To avoid having to retype the wall time parameters whenever Ziggy wants a different architecture, enter the wall time parameters that would be valid in the absence of concurrency (i.e., how long it would take to run your algorithm on 1 CPU), and set `wallTimeScaling` to true. When this is set, Ziggy knows that it has to scale the actual wall time per subtask down based on the number of CPUs in each node. Ziggy will assume a simple linear scaling, i.e.: true wall time = wall time parameter / CPUs per node. This probably isn't quite correct, but hopefully is correct enough.
+
+#### A Note on Setting Optional Parameters
+
+When you set some of the optional parameters, Ziggy will compute values for all the rest. Ziggy has 2 requirements for this process. First, it **must** use any optional parameter values you specify, it can never change those values. Second, it **must** produce a result that allows the task to run to completion. That is, it has to select enough compute nodes and enough wall time to process all of the subtasks.
+
+A close reading of the paragraph above reveals a potential problem: what happens if you specify a set of parameters that makes it impossible for Ziggy to satisfy that second requirement? Just as an example, if you set the maximum number of nodes, the compute node architecture, and the requested wall time, it's possible to ask for so little in total compute resources that it's impossible to run all of your subtasks!
+
+If Ziggy determines that it can't ask for enough resources to run your task, it will throw an exception at runtime, and your pipeline will stop. You'll then need to adjust the parameters and restart.
+
+The best way to avoid this outcome is to use the [remote execution dialog](remote-dialog.md) to set the optional parameters. The remote execution dialog won't allow you to save your parameter values if they result in tasks that can't finish because they're starved of compute resources.
diff --git a/doc/user-manual/rerun-task.md b/doc/user-manual/rerun-task.md
new file mode 100644
index 0000000..7698397
--- /dev/null
+++ b/doc/user-manual/rerun-task.md
@@ -0,0 +1,53 @@
+## Re-Run or Resume a Failed Task
+
+Let's assume at this point that you've investigated the cause of your failed task. Maybe you've made software changes that address the problem, so now you want to re-run the failed task. Or maybe you've decided that it's not worth the effort to try to salvage the failed subtasks but you would like to capture the results of the successful ones. In either case, the restart dialog is what you need to use.
+
+### The Restart Dialog Box
+
+To view the restart dialog box, select your failed task or tasks, right click, and select `Restart` from the tasks menu. You'll see this:
+
+![](images/restart-dialog.png)
+
+If you right-click on the `Restart Mode` field, you'll get a menu that offers several restart options.
+
+#### Restart from Beginning
+
+This does pretty much what it sounds like. Go back, redo marshaling, then run the algorithm code, etc. It's pretty unlikely that you'll want to use this option unless you've made a change that will cause marshaling to retrieve different inputs, but it's there if you need to do that.
+
+If you restart a task from the beginning, this will also wipe out the information on which subtasks failed and which were successful, so Ziggy will have to re-run all the subtasks.
+
+#### Resubmit
+
+This resumes execution immediately after marshaling, when Ziggy hands off execution to the algorithm management system. This is the option you'll want to use if you've made a code change that will address the problem.
+
+Note that if you do this, only the failed subtasks will be run! All the subtasks that ran successfully will be left alone. If your code change was such that you want to go back and re-run those other subtasks as well, you'll need to launch a new pipeline instance or use the Restart from Beginning option.
+
+#### Resume Current Step
+
+This tells Ziggy to try to pick up where it left off, in effect to force the P-state to advance and see what happens.
+
+Generally the case where you'd use this is the one we're in now: some subtasks ran, others didn't, the task made it to `ALGORITHM_COMPLETE` and then halted. Selecting this option will cause Ziggy to advance to `STORING`, at which point it will store the results from the 3 successful subtasks and abandon efforts to get results from the failed one.
+
+#### Resume Monitoring
+
+In somewhat unusual cases, it may happen that the monitoring subsystem will lose track of what's going on with one or more tasks. In this case, you may see signs that execution is progressing, but the console doesn't show any updates. In this case, the `Resume Monitoring` option tells the monitoring subsystem to try to reconnect with the running task.
+
+### Restarting Multiple Tasks
+
+If you have a bunch of tasks for a given node, it may be that some will fail while others don't, and you want to restart all the failed tasks. There are two ways to do this.
+
+The first is to select all the failed tasks from the tasks menu (by shift-clicking or control-clicking), then right-click to get the tasks menu and select `Restart`. Note that this allows you to also restart a subset of failed tasks, which you might want to do. You can select the restart mode for each task you restart this way.
+
+The other option, when you know that you want to restart all the failed tasks, is to select the instance from the instance table on the left, right-click to bring up the instances menu, and select the Restart all failed tasks menu item. In this case all tasks will use the same restart mode.
+
+### Note on Restarting the Failed Permuter Task
+
+Remember how we started all this by ordering subtask 0 in each `permuter` task to fail? Here's an annoying subtlety: there's no way to get `permuter` to successfully run that subtask!
+
+Why not?
+
+If the subtask had failed because of a real problem, we would be able to fix the problem and resubmit the task, or restart from the beginning. But what actually happened is that we set a module parameter that told `permuter` to throw an exception in subtask 0.
+
+If we re-run the task, it will re-run with the same values for all parameters (except for the remote execution parameters, but we're not using those at all for this example). This means that the `throw_exception_subtask_0 parameter` will still be true, and subtask 0 will fail again.
+
+In real life, it's possible that you'll encounter a situation like this one, in which a task fails and the only way to get it to run successfully is to change the values of some module parameters. In that case, you won't be able to re-run because re-run doesn't let you change the parameters. In that case, you'll need to change the parameters and use the triggers panel to start a new pipeline instance. In the more common cases (software bug that had to be fixed, failure due to some sort of hardware problem, etc.), re-running a task offers the possibility of getting failed subtasks to run to completion.
\ No newline at end of file
diff --git a/doc/user-manual/running-pipeline.md b/doc/user-manual/running-pipeline.md
new file mode 100644
index 0000000..72816b6
--- /dev/null
+++ b/doc/user-manual/running-pipeline.md
@@ -0,0 +1,162 @@
+## Running the Cluster
+
+At last, we are ready to start up the pipeline! First, though, we need to have a minor digression about the `runjava` program.
+
+### Runjava
+
+Running a piece of Java code should be simple: you use the `java` command at the command line, followed by the class name. Awesome!
+
+Well, almost. In reality, there are often issues with the `java` command: specifying the correct class path, giving the fully-qualified name of the class (without typos), etc.
+
+For this reason, Ziggy provides a program that inserts a layer of abstraction over the `java` command. This layer of abstraction allows the program to use the Ziggy configuration system (like the properties file) to specify the things that the user would otherwise need to put at the command line. It also allows for nicknames: rather than the fully-qualified names, shorter names are provided for some classes. It's like having a version of the `java` command that's purpose-built to work with Ziggy.
+
+The name of this software is `runjava`, and it's a Perl program that's in Ziggy's `build/bin` directory (the Ziggy one, not the sample pipeline one). The `runjava` program is so useful that you should probably create an alias for it, or else add Ziggy's `build/bin` directory to your search path. Just so you don't have to type the whole path to `runjava` every time you want to use it.
+
+You can see the full set of nicknames by running the runjava command with no arguments:
+
+```console
+$ runjava
+ NICKNAME CLASSNAME
+ cluster gov.nasa.ziggy.ui.ClusterController
+ compute-node-master gov.nasa.ziggy.module.ComputeNodeMaster
+ console gov.nasa.ziggy.ui.ZiggyConsole
+ dump-err gov.nasa.ziggy.module.io.DumpMatlabErrCli
+ dump-props gov.nasa.ziggy.services.configuration.DumpSystemProperties
+dump-system-properties gov.nasa.ziggy.services.config.DumpSystemProperties
+ execsql gov.nasa.ziggy.dbservice.SqlRunner
+ generate-manifest gov.nasa.ziggy.data.management.Manifest
+ metrics gov.nasa.ziggy.metrics.report.MetricsCli
+ pd-export gov.nasa.ziggy.pipeline.definition.PipelineDefinitionCli
+ pd-import gov.nasa.ziggy.pipeline.definition.PipelineDefinitionCli
+ pe-import gov.nasa.ziggy.services.events.ZiggyEventHandlerDefinitionFileImporter
+ perf-report gov.nasa.ziggy.metrics.report.PerformanceReport
+ pl-export gov.nasa.ziggy.parameters.ParameterLibraryImportExportCli
+ pl-import gov.nasa.ziggy.parameters.ParameterLibraryImportExportCli
+ pl-override gov.nasa.ziggy.parameters.ParameterLibraryOverrideCli
+ pt-import gov.nasa.ziggy.data.management.DataFileTypeImporter
+ seed-security gov.nasa.ziggy.services.security.SecuritySeedData
+ spoc-version gov.nasa.ziggy.common.version.ZiggyVersionCli
+ status-listener gov.nasa.ziggy.services.process.StatusMessageLogger
+$
+```
+
+### Runjava Cluster Commands
+
+For coarse, on-off control for the cluster, the fundamental `runjava` command is `runjava cluster`. As described [elsewhere](pipeline-architecture.md), **cluster** is the word we use to refer to a particular instance of a pipeline, its data storage, etc.
+
+To see the options for this command, do `runjava cluster`with no additional arguments:
+
+```console
+$ runjava cluster
+usage: ClusterController [options] command...
+
+Commands:
+init Initialize the cluster
+start Start the cluster
+stop Stop the cluster
+status Check cluster status
+console Start pipeline console GUI
+
+Options:
+ -f,--force Force initialization if cluster is already
+ initialized
+ --workerHeapSize Total heap size used by all workers (MB)
+ --workerThreadCount Number of worker threads or 0 to use all
+ cores
+$
+```
+
+The first thing you need to do is to initialize the cluster (hence the name).
+
+#### Cluster Initialization
+
+Cluster initialization is something you only do once per cluster. It populates the database with Ziggy's standard tables; sets up the datastore directories; and reads in the pipeline configuration from the XML files.
+
+The command `runjava cluster init` causes the initialization to occur. When you do this, after a few seconds you'll either have a nice spew of logging that ends with the message:
+
+`[ClusterController.initializeCluster] INIT: database initialization and creation complete`
+
+or else you'll see a Java stack trace that tells you that something went wrong. The most common problem that causes initialization to fail is a problem with the XML files that describe the pipeline, so that's the place to look first. Hopefully the exception provides informative messages that will help you track down the problem (they help me, but YMMV).
+
+Once you've initialized the cluster, Ziggy will prevent you from doing so again:
+
+```
+system:sample-pipeline user$ runjava cluster init
+[ZiggyConfiguration.getConfiguration] Loading configuration from: /xxx/yyy/zzz/ziggy/sample-pipeline/etc/sample.properties
+[ZiggyConfiguration.getConfiguration] Loading configuration from: /xxx/yyy/zzz/ziggy/sample-pipeline/etc/../../build/etc/ziggy.properties
+Cannot re-initialize an initialized cluster without --force option
+system3465:sample-pipeline user$
+```
+
+Like it says: if you want to do this, you can do it; you need to use the `--force` (or `-f`) option. Be aware, though: **if you reinitialize the cluster, you will delete all of the existing contents!** That's why Ziggy tries to keep you from doing it by accident. Reinitialize and all the database content, all the data files that have been generated as results -- all gone.
+
+That said: if your cluster initialization fails because of a problem in the XML, you will need to use the `--force` option. This is because the database was successfully created with all its tables. Thus, even though you failed to put actual information in the tables, you'd need to use the `--force` option to try again.
+
+##### What if I don't want to reinitialize?
+
+If the failure was in the import of the contents of the pipeline-defining XML files, there's an alternative to using `runjava cluster init`. Specifically, you can use other runjava commands that import the XML files without performing initialization.
+
+If you look at the list of runjava nicknames in the top screen shot, there are 3 that will be helpful here: `pl-import`, `pt-import`, and `pd-import`. These do what they say: import the parameter library, data type definition, and pipeline definition files, respectively.
+
+Important note: if you decide to manually import the XML files, **you must do so in the order shown above:** parameters, then data types, then the pipeline definitions. This is because some items can't import correctly unless other items that they depend upon have already been pulled in.
+
+#### Cluster Start and Cluster Status
+
+To start the cluster, you use -- wait for it -- `runjava cluster start`. If you're using a private (non-system) database, this command starts it. The worker process is then started. The worker, as the name suggests, does all of Ziggy's actual work: generates tasks, starts them running, monitors them, persists outputs, records instances of failures, and much, much more. It's an exaggeration to say that the worker **is** Ziggy, but not much of one.
+
+Once you've done this, both the worker and the database software run as what Linux folks like to call "daemons". This means that they're running even though they gave you back the command prompt in the shell where you started them. To see this, use runjava cluster status:
+
+```console
+[ZiggyConfiguration.getConfiguration] Loading configuration from: /xxx/yyy/zzz/ziggy/sample-pipeline/etc/../../build/etc/ziggy.properties
+[ClusterController.status] Cluster initialized
+[ClusterController.status] Worker running
+[ClusterController.status] Database running
+[ClusterController.status] Cluster running
+sample-pipeline$
+```
+
+The status command shows that the cluster is running, which is great! But so far it's not obvious how you're supposed to do anything with it. To make use of the cluster, you'll need Ziggy's graphical user interface.
+
+##### Threads and Heap Size
+
+If you look at the command options, there are two options that are relevant to starting the cluster. These are `workerHeapSize` and `workerThreadCount`.
+
+The `workerHeapSize` is the number of MB that the worker process is allowed to use. If you ever encounter an error message complaining that the worker is out of memory or out of "GC Overhead" (i.e., additional RAM needed by the Java garbage collector), you can stop the cluster, then restart it with a larger heap size specified via this option.
+
+Ziggy's worker is a multi-threaded application. This means that the worker can marshal, persist, and execute multiple tasks simultaneously. The maximum number of tasks that can execute in parallel is set by the `workerThreadCount` option.
+
+Note that these options are, well, optional. The pipeline properties file specifies a default value for the heap space and a default for the thread count. If you don't use these options at the command line, Ziggy will use the values in the properties file. For more information, see the article on [The Properties File](properties.md).
+
+How should you decide how to set these values? Here are a few rough rules of thumb:
+
+1. Your pipeline will have a maximum number of tasks that it will ever need to execute for a given node in the pipeline. This is determined by your design of the pipeline. In the sample pipeline, there are 2 datasets that get processed in parallel, `set-1` and `set-2`. Thus the sample pipeline can benefit from having 2 worker threads, but wouldn't be any faster if it had more.
+2. The system that runs the worker process will have a fixed number of CPUs ("cores"). As a general rule, there is no benefit to selecting a worker thread count that is larger than the number of CPUs; this will simply result in too many threads competing for too few CPUs.
+3. The more threads you have, the larger the heap will need to be in order to support them. That said, you can probably get by with 1-2 GB per worker thread.
+
+One last detail on this: the worker heap size controls the amount of RAM available to the worker threads, and thus the amount of RAM available for marshaling and persisting. It doesn't control the amount of RAM available to the algorithms. The reason for this is technical and probably not all that interesting, but the point is that even if you know that your algorithm needs 10 GB per task, you still can give the worker 1-2 GB per thread and everything will run just fine.
+
+#### Cluster console
+
+To get the console to show up use the command `runjava cluster console`. After a few seconds and a few lines of logging, you'll see this appear:
+
+![](images/gui.png)
+
+Now you're ready to do some stuff!
+
+##### Runjava cluster start console &
+
+Personally, I find that when I start the pipeline I generally want a console window immediately. For this reason, you can put the two commands together: `runjava cluster start console & `. That final `&` sign causes the command to run in the background so you get your command prompt back in the window where you issued the command. So that you don't go around filling your display with terminal windows that are just there to keep the console running.
+
+##### Important Note: The Cluster and the Console
+
+It is important to remember that the console is not the cluster! Most importantly, when you close a console window, the cluster continues to run even though the console is gone (so you have no way to control it, but it's still doing whatever you set it working on before you closed the console). To stop the cluster, you use:
+
+#### Cluster stop
+
+When you're done with the cluster and want to shut it down, the command is `runjava cluster stop`. This will stop the worker and stop the database (for a non-system database). It will also cause any console windows to shut down.
+
+That said: don't do it now! Because you'll want the cluster running and the console up when we ...
+
+### What's Next?
+
+... [use the console](ziggy-gui.md) to do some cool stuff!
diff --git a/doc/user-manual/select-hpc.md b/doc/user-manual/select-hpc.md
new file mode 100644
index 0000000..4491b0a
--- /dev/null
+++ b/doc/user-manual/select-hpc.md
@@ -0,0 +1,93 @@
+### High Performance Computing Overview
+
+Large data volumes and/or compute-intensive processing algorithms demand the use of equally large-scale computing hardware to perform the processing. Fortunately, Ziggy supports the use of High Performance Computing (HPC) facilities.
+
+### What Kind of HPC Facilities?
+
+"High Performance Computing" can mean a bunch of things, including commercial cloud computing systems. In this case, it specifically means [the systems at the NASA Advanced Supercomputer (NAS)](https://www.nas.nasa.gov/hecc/).
+
+At the moment, the NAS is the only supported HPC option for Ziggy. This is mainly because it's the only one that any of the Ziggy developers have had access to, and it's hard to support an HPC system if you can't even log on to it. That said, if you have such a system and would like us to adapt Ziggy to use it, I'm sure we could do that without much ado (assuming you get us accounts).
+
+In the near future the Ziggy team hopes to add cloud computing support for cloud systems that are supported by NASA. We'll keep you posted.
+
+### The RemoteParameters Parameter Set
+
+Let's look again at the XML that defined the sample pipeline. At one point, you can see this:
+
+```xml
+
+
+
+
+
+
+
+```
+
+ The parameter set `Remote Parameters (permute color)` is defined in the parameter library XML file:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+The parameter set has a Java class associated with it, which as you've probably gathered means that it's a parameter set that Ziggy uses for its own management purposes.
+
+#### Enabling Remote Execution
+
+If you change the value of `enabled` to true, then any node that includes this parameter set will try to run on HPC.
+
+That's all it takes. Well, that and access to the NAS.
+
+Note the implications here. First, for a given pipeline the user can select which nodes will run on HPC and which will run locally (here "locally" means "on the system that hosts the worker process", or more generally, "on the system where the cluster runs"). Second, even for nodes that have an instance of `RemoteParameters` connected to them, you can decide at runtime whether you want to run locally or on HPC!
+
+The RemoteParameters class is discussed in greater detail in [the Remote Parameters article](remote-parameters.md).
+
+#### HPC Execution Flow
+
+When Ziggy runs a task that's configured to go on HPC, it issues commands to the HPC's batch system (in the case of the NAS, that's the [Portable Batch System](https://www.altair.com/pbs-professional), or PBS). The PBS commands specify the number of compute nodes to be used and the allowed wall time (i.e., how long you have the use of the nodes before the HPC repos them). While the PBS commands are being issued, the task status will be `ALGORITHM_SUBMITTING`, or `As`. Once each PBS submission has been accepted, the task will go to `ALGORITHM_QUEUED`, or `Aq`. Depending on how heavily loaded the queues are, sooner or later your jobs will start to run on the HPC system, at which time the task goes to `ALGORITHM_EXECUTING`, `Ae`.
+
+#### Tasks and Jobs
+
+Depending on your parameters, it's often the case that your remote execution of a task will need more than 1 compute node. HPC systems generally allow users to submit jobs that request multiple nodes; nevertheless, Ziggy doesn't do this. What it does is submit multiple jobs, each of which requests 1 node. Each of these 1-node jobs has a system by which it identifies subtasks that need processing and allocates them to itself as resources permit.
+
+The reason we do it this way is that it improves throughput: if the HPC system has a single available node, and it has a job that only needs 1 node, it will often allocate that node to that job. If, on the other hand, you have a job that needs 10 nodes, you wind up waiting until 10 nodes are available. With more small jobs you spend less time waiting for execution to begin.
+
+For the most part, you don't really need to know about this except for the fact that each of the jobs has its own algorithm log file. If you have, say, 10 jobs that are processing task 12 in instance 3, and the module is permuter, then the algorithm logs will be `3-12-permuter.0-1.log`, `3-12-permuter.1-1.log`, ... `3-12-permuter.9-1.log`. When you use the GUI to display logs, all of these logs will appear as separate entries.
+
+#### When Your Jobs Time Out
+
+If you haven't set the parameters for remote execution correctly, it's possible that your HPC jobs will reach their wall time limits and exit while there are still subtasks waiting to be processed. If this happens, the task will go to state `ERROR` and P-state `Ac` (`ALGORITHM_COMPLETE`). What then?
+
+One option is to resubmit the task to PBS via the `Resubmit` option on the task menu (see the article [Re-run or Resume a Failed Task](rerun-task.md) for details). When you do this, Ziggy will automatically adjust the PBS request based on how many subtasks are left. That is, if 80% of subtasks ran successfully, on resubmit Ziggy will only ask for 20% as much compute capacity as it asked for the first time.
+
+Given that the first request didn't have enough resources to run the whole task, it's likely the second one won't have enough to run the remaining tasks. Given this, before you resubmit you might want to adjust the parameters so that you get more nodes and/or wall time.
+
+BUT WAIT! Didn't we say earlier that once a parameter set is sent to a pipeline instance, that instance will always use the set as it was at that moment? That is, that changes to the parameters don't get propagated to existing instances and tasks?
+
+Yes, we did, and that's true. In general. But the exception is `RemoteParameters`. When Ziggy runs or reruns tasks, it always goes and gets the current values of any `RemoteParameters` instance. This is because we've encountered this exact issue so frequently that we realized that it's essential to be able to fiddle with the parameters that are used for remote job submissions. Also, the values in `RemoteParameters` can't affect the science results. Because of this, there's no need to rigorously track the exact values of `RemoteParameters` used in any particular processing activity.
+
+In the event that a really small number of subtasks haven't run when your jobs exit, there's something else you can do: you can set `enabled` to false and resubmit the task. The remaining subtasks will then run locally! This can be useful in cases where there's so few subtasks left that they can't even use up a whole compute node, or if the run time on the local system is so small that it makes sense to run locally rather than going back into the PBS queue.
+
+##### Options for Automatic Resubmission
+
+In the discussion above, we suggested that, in the event that execution times out, you can resubmit the task and the subtasks that didn't run to completion will then be run (subject to the possibility that some of them will then also time out). We also offered the option to disable remote execution if the number of subtasks gets small enough that your local system (i.e., the system that runs the worker process) can readily handle them. This is all true, but it can be a nuisance. For this reason, there are some additional options to consider.
+
+First: Ziggy can automatically resubmit tasks that don't complete successfully. This is discussed in [the article on TaskConfigurationParameters](task-configuration.md). You can specify that, in the event that a task doesn't complete, Ziggy should resubmit it. In fact, you can specify the number of times that Ziggy should resubmit the task: after the number of automatic resubmits is exhausted, the task will wait in the ALGORITHM_COMPLETE processing state for you to decide what to do with it (i.e., try to resubmit it again, or fix underlying software problems, or decide that the number of completed subtasks is sufficient and that you want to move on to persisting the results).
+
+At this point, we need to reiterate the warning in the TaskConfigurationParameters article regarding this parameter: Ziggy can't tell the difference between a task that didn't finish because it ran out of wall time and a task that didn't finish because of a bug in the algorithm code somewhere. If there's an algorithm bug, Ziggy will nonetheless resubmit an incomplete task (because Ziggy doesn't know the problem is a bug), and the task will fail again when it hits that bug.
+
+Second: Ziggy allows you to automate the decision on whether a given number of subtasks is too small to bother with remote execution. The automation is a bit crude: as shown in [the RemoteParameters article](remote-parameters.md), there is an option, `minSubtasksForRemoteExecution`. This option does what it says: it sets the minimum number of subtasks that are needed to send a task to remote execution; if the number of subtasks is below this number, the task will run locally, **even if enabled is set to true**!
+
+By using these two parameters, you can, in effect, tell Ziggy in advance about your decisions about whether to resubmit a task and whether to use remote execution even if the number of subtasks to process is fairly small.
+
+
+
diff --git a/doc/user-manual/start-end-nodes.md b/doc/user-manual/start-end-nodes.md
new file mode 100644
index 0000000..78dc48a
--- /dev/null
+++ b/doc/user-manual/start-end-nodes.md
@@ -0,0 +1,15 @@
+## Changing the Start and End Nodes
+
+As you perform your data processing activities, it won't always be necessary to run the whole, entire pipeline. As a simple example: any time you're reprocessing, you won't need to re-run the data receipt step in any pipelines that start with data receipt.
+
+Fortunately, Ziggy provides tools that allow you to manage these conditions.
+
+To see how to do this, return to the `Triggers` panel, select the `sample` pipeline, and click the `Fire` Button. This time, though, when the Launch Pipeline dialog box appears, don't hit the `Fire!` button on the dialog box just yet. Instead, click the `Override Start` and `Override Stop` check boxes. This will allow you to use the pull-down menus to select a different start and end node. For example:
+
+
+
+Now press the `Fire!` button and return to the `Instances` panel. The pipeline will be running instance 2, which will start with the `permuter` tasks. Eventually the panel will look like this:
+
+![](images/gui-start-end-adjusted.png)
+
+As advertised, the pipeline ran again but this time it skipped data receipt and stopped short of the averaging tasks.
diff --git a/doc/user-manual/start-pipeline.md b/doc/user-manual/start-pipeline.md
new file mode 100644
index 0000000..46e0458
--- /dev/null
+++ b/doc/user-manual/start-pipeline.md
@@ -0,0 +1,53 @@
+## Starting a Pipeline
+
+Let's do this.
+
+### Get to the Triggers Panel
+
+On the top-left of the console is a tab, `Operations`. Select that and you should see this:
+
+![](images/ops-panel.png)
+
+You see that there are now 2 tabs within the operations panel: `Instances` (where we are now) and `Triggers`. Select `Triggers` and you'll see this:
+
+![](images/triggers.png)
+
+The table lists all the pipelines that are defined on this cluster, which in this case is just one, the sample pipeline. Since that's the only one we've got, select it and click the `Fire` button.
+
+### Launch the Pipeline
+
+A new dialog box will pop up that looks like this:
+
+
+
+A lot of options! For now, just put some kind of identifying text in the `Pipeline Instance Name` text box and press the `Fire!` button.
+
+### Monitor Progress
+
+As soon as the dialog box disappears, click back to the `Instances` panel. The left side should look something like this:
+
+![](images/instances-running.png)
+
+Note: I cheated a bit and clicked on the one row in the table on the left. On the right you see this:
+
+![permuter-tasks](images/permuter-tasks.png)
+
+Notice a few things:
+
+1. In the upper-right corner, the grey lights for `Pi` (pipeline) and `W` (worker threads) have turned green. These 4 lights are known as Ziggy's "stoplights" (or somewhat more derisively, "idiot lights"). The first green light means that a pipeline is running; the second one means that one or more of Ziggy's worker threads are occupied. For more information, take a look at the article on the [Monitoring Tab](monitoring.md).
+2. The left-hand table is now populated. It shows one entry, with ID 1. This is known as the `Instances` table.
+3. There are 2 right-hand tables that are populated as well.
+ 1. The lower table is the "tasks table." It shows that data receipt already ran to completion, and that there are 2 permuter tasks that are running in parallel.
+ 2. The upper table is the "scoreboard." It shows -- something. Actually, the tasks table is pretty cryptic as well.
+
+As you watch, the mysterious numbers in the last column of the task table change from `Ae (4 / 0 / 0)` to `Ae (4 / 1 / 0)`. The mysterious numbers in the scoreboard change in unison with the ones in the task table. Eventually the two peruter tasks each hits `Ae (4 / 4 / 0)`. When that happens, they instantly change to `C (4 / 4 / 0)`, the State columns turn to `COMPLETED`, and two new tasks appear:
+
+![flip-tasks](images/flip-tasks.png)
+
+Finally, after a few more seconds, two new tasks, named `averaging`, appear. Shortly after that, we get to this state:
+
+![tasks-done](images/tasks-done.png)
+
+The pipeline and worker lights are grey again, the instance and all the tasks show `COMPLETED`. Congratulations! You've just run your first Ziggy pipeline!
+
+At this point, you'd probably like an explanation of just what everything on the `Instances` panel is trying to tell you. If so, read on! Specifically, the article on [The Instances Panel](instances-panel.md).
diff --git a/doc/user-manual/system-requirements.md b/doc/user-manual/system-requirements.md
new file mode 100644
index 0000000..b1bb2a6
--- /dev/null
+++ b/doc/user-manual/system-requirements.md
@@ -0,0 +1,22 @@
+# Prerequisites and System Requirements
+
+### Operating System
+
+Ziggy is supported on Linux and Mac OS.
+
+For Linux, the following versions have been tested: SLES 11, SLES 12, RHEL 7, RHEL 8, and Debian through version 11 (bullseye). That said, we expect that any relatively recent version of Linux will suffice.
+
+For Mac OS, All versions from Snow Leopard (10.6) to Monterey (12.6) have been used to build and run Ziggy. Both Intel and Apple M1 CPUs have been used. Compability of Ventura (13.x) is to be determined.
+
+### RAM and Disk Space
+
+Ziggy itself is quite lightweight, hence we expect that the RAM and disk space requirements will be set by the dataset you want to process and the algorithms it runs, not by Ziggy. That said: we have run Ziggy on laptops with 16 GB RAM, and the total disk space footprint for Ziggy when fully built is under 3 GB.
+
+### Compilers and Interpreters
+
+Ziggy requires the following computer language resources:
+
+- Java: JDK 1.8. Note that later versions of the JDK have some changes to their included libraries that have not yet been addressed in the Ziggy build system.
+- Perl 5.16 or later.
+- C/C++ compiler that supports the 2011 C++ standard. Historically we've used gcc but we expect that any compiler that's 2011-compliant will do. We've also used clang, just to prove that we could.
+- Pythyon 3 and Pip 3. Note that these are only for the sample pipeline, not for Ziggy itself.
diff --git a/doc/user-manual/task-configuration.md b/doc/user-manual/task-configuration.md
new file mode 100644
index 0000000..67058b5
--- /dev/null
+++ b/doc/user-manual/task-configuration.md
@@ -0,0 +1,150 @@
+## The Task Configuration Parameter Sets
+
+If you look back at [what happened when we ran the pipeline](start-pipeline.md), you'll note that each of the nodes after data receipt -- permuter, flip, and averaging -- had 2 tasks; and in each case there was one task with a `UOW` of 1 and another with `UOW` 2. You may have wondered: how does this work? How did Ziggy decide how to divide up the work into tasks?
+
+Excellent question! We will now answer that question. We'll also show you some other cool features that are all controlled by the parameter sets that manage task configuration.
+
+### Task Configuration in XML Files
+
+If you go back to pl-sample.xml, you'll see a parameter set that looks like this:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+Then, looking at pd-sample.xml, if you swim down to the spot where the permuter node of the sample pipeline is defined, you where this parameter set is referenced:
+
+```xml
+
+
+
+
+
+
+
+```
+
+And finally, if you recall from [the datastore layout](datastore-task-dir.md), there are directories under the datastore root directory named `set-1` and `set-2`.
+
+Put it all together, and you can sorta-kinda see what happened. I'll spell it out for you here:
+
+- The permuter node uses the `Multiple subtask configuration` to, uh, configure its tasks.
+- The `Multiple subtask configuration` parameter set has a regex of `set-([0-9]{1})` that is used to define task boundaries.
+- Every directory that matches the regex is turned into a pipeline task. There are 2 directories in the datastore that match the regex: `set-1` and `set-2`. Thus, there's 1 task for `set-1` and another for `set-2`.
+- The permuter uses `raw data` as its input data file type. Ziggy goes into the set-1 directory and looks for all the files that match the datastore file name specification. These are `set-1/L0/nasa-logo-file-0.png`, et. al. Ziggy moves copies of these files into the task directory for the `set-1` task. The `set-2` task files are procured the same way.
+- The task directory regex has a regex group, `([0-9]{1})`. This is used as a label for the UOW column of the tasks table on the instances panel. Thus when the tasks show up on the instances panel, one is called `1` and the other `2`.
+
+Every node needs an instance of `TaskConfigurationParameters`, so you may well wind up with more than one in your `pl-*.xml` files. The sample pipeline has a total of 3.
+
+#### How to Run a Subset of Tasks
+
+What happens if I want to run the `set-1` tasks but not the `set-2` tasks? All I have to do is change the regex so that only `set-1` matches. In other words, I want `taskDirectoryRegex="set-1"`.
+
+This remains the case for more complicated arrangements. Consider for example what would happen if we had `set-1`, `set-2`, ... `set-9`. Now imagine that we want to run `set-1`, `set-3`, and `set-6`. In that case the regex would be `taskDirectoryRegex="set-([136]{1})"`.
+
+#### Datastore Directory Organization
+
+For the sample pipeline, we put the dataset directory at the top level, the `L0`, `L1`, etc., directories at the next level down. What if I wanted to do it the other way around? That is, what if I wanted `L0`, `L1`, etc., at the top and `set-1` and `set-2` under those? We can do it that way, sure!
+
+First, the datastore would need to change, which means that the `fileNameWithSubstitutionsForDatastore` entries in the data file type definitions need to change. For example: for raw data, instead of `"$2/L0/$1-$3"`, it would need to be `"L0/$2/$1-$3"`; and so on for the other data file type definitions.
+
+Second the `taskDirectoryRegex` values would have to change. For the permuter, the regex would need to be `"L0/set-([0-9]{1})"`. This is because the task directory regex needs to match the layout of the datastore and the naming conventions of the data file types.
+
+There are two major downsides to organizing the datastore by processing step / data set rather than data set / processing step:
+
+First, if you look at the pipeline definition, you can see that I use the same task configuration parameter set for both permuter and flip nodes. I can do that because in both cases, the regex for task generation is the same: `"set-([0-9]{1})"`. If I exchanged the order of directories, then permuter would need `"L0/set-([0-9]{1})"`, while the one for flip would be `"L1/set-([0-9]{1})"`.
+
+Second, consider the situation in which I want to process `set-1` but not `set-2` through both permuter and flip. With the current setup, that's easy to do because they use the same task configuration parameters: I change the one regex, and both pipeline modules are affected. If I had a separate task configuration parameter set for each pipeline node, then I'd need to adjust the regex in each set to get the same effect. It's less convenient and introduces a greater chance of pilot error.
+
+The moral of this story is that it's worthwhile to think a bit about the datastore organization, in particular thinking about what operations should be easy to group together.
+
+### All Those Other Parameters
+
+So far we've discussed only 1 parameter in the task configuration parameter set. What about all the others? Let's walk through them.
+
+#### singleSubtask
+
+For permuter and flip, we wanted 1 subtask for each image, so 4 subtasks in each task. The averaging algorithm is different: in that case, we're averaging together a bunch of images. That means that we want all the data files to get processed together, not in a subtask each (I mean, you can average together 1 file, but it's kind of dull).
+
+What this tells us is that Ziggy needs to support algorithms that get executed once per task and process all their data in that single execution (like the averaging algorithm), as well as algorithms that get executed multiple times per task and process one input file in each execution (like permuter and flip). The choice between these two modes of execution is controlled by the `singleSubtask` parameter. Setting this to true tells Ziggy that all the data files for the task are processed in 1 subtask, not multiple. Note that the averaging node has a different task configuration parameter set, and that set has `singleSubtask` set to true.
+
+The default for `singleSubtask` is `false`.
+
+#### maxFailedSubtaskCount
+
+Under normal circumstances, as we'll see shortly, if even a single subtask fails then the entire task is considered as failed and the operator has to intervene. However, it may be the case that the mission doesn't want to stop and deal with the issue if only a few subtasks fail. Imagine your task has 5,000 images in it, and the requirements state that the mission shall process at least 90% of all images successfully. Well, you can set `maxFailedSubtaskCount` in that case to 500. If the number of failed subtasks is less than 500, then Ziggy will declare the task successful and move on to the next processing activity; if it's 501 or more, Ziggy will declare the task failed and an operator will need to address the issue.
+
+The default for `maxFailedSubtaskCount` is zero.
+
+The default for `maxFailedSubtaskCount` is zero.
+
+#### reprocess
+
+Most missions don't get all their data all at once. Most missions get data at some regular interval: every day, every month, whatever.
+
+In that case, a lot of the time you'll only want to process the new data. If you've got 5 years of data in the datastore, and you get a monthly delivery, you don't want to process the 5 years of data you've already processed; you want to process only the data you just got. However: it may be that every once in a while you'll want to go back and process every byte since the beginning of the mission. Maybe you do this because your algorithms have improved. Maybe it's time series data, and you want to generate time series across the whole 5 years.
+
+The `reprocess` parameter controls this behavior. When `reprocess` is true, Ziggy will try to process all the mission data, subject to the `taskDirectoryRegex` for each pipeline node. When it's set to false, Ziggy will only process new data.
+
+The default for `reprocess` is `false`.
+
+##### How does Ziggy Know Whether Data is "New" or "Old"?
+
+Excellent question! Here's a long answer:
+
+Ziggy keeps track of the "producer-consumer" information for every file in the datastore. That is, it knows which pipeline task produced each file, and it knows which pipeline tasks used each file as input ("consumed" it). Ziggy also knows what algorithm was employed by every task it's ever run.
+
+Thus, when Ziggy marshals inputs for a new task, and that task's `TaskConfigurationParameters` instance has `reprocess` set to `false`, the system filters out any inputs that have been, at some point in the past, processed using the same algorithm as the task that's being marshaled. Result: a task directory that has only data files that haven't been processed before.
+
+One subtlety: Ziggy not only tracks which pipeline tasks consumed a given file as input; it also looks to see whether they did so successfully. If a task consumes a file as input, but then the algorithm process that was running on that data bombs, Ziggy doesn't record that task as a consumer of that file. That way, the next time Ziggy processes "new" data, it will also include any "old" data that was only used in failed processing attempts.
+
+The default for `reprocess` is `false`.
+
+##### How does Ziggy Know Whether Data is "New" or "Old"?
+
+Excellent question! Here's a long answer:
+
+Ziggy keeps track of the "producer-consumer" information for every file in the datastore. That is, it knows which pipeline task produced each file, and it knows which pipeline tasks used each file as input ("consumed" it). Ziggy also knows what algorithm was employed by every task it's ever run.
+
+Thus, when Ziggy marshals inputs for a new task, and that task's `TaskConfigurationParameters` instance has `reprocess` set to `false`, the system filters out any inputs that have been, at some point in the past, processed using the same algorithm as the task that's being marshaled. Result: a task directory that has only data files that haven't been processed before.
+
+One subtlety: Ziggy not only tracks which pipeline tasks consumed a given file as input; it also looks to see whether they did so successfully. If a task consumes a file as input, but then the algorithm process that was running on that data bombs, Ziggy doesn't record that task as a consumer of that file. That way, the next time Ziggy processes "new" data, it will also include any "old" data that was only used in failed processing attempts.
+
+#### reprocessingTasksExclude
+
+Okay, this one's a bit complicated.
+
+Imagine that you ran the pipeline and everything seemed fine, everything ran to completion. But then a subject matter expert looks at the results and sees that some of them are good, some are garbage. After some effort, the algorithm code is fixed to address the problems with the ones that are garbage, and you're ready to reprocess the tasks that produced garbage outputs.
+
+How do you do that?
+
+If you select `reprocess="false"`, nothing will get processed. As far as Ziggy is concerned, all the data was used in tasks that ran to completion, so there's nothing that needs to be processed.
+
+If you select `reprocess="true"`, both the inputs that produced garbage and the ones that didn't will get reprocessed. If the fraction of inputs that produced garbage is small, you may not want to do this.
+
+What you do is set `reprocess="true"`, but then you tell Ziggy to exclude the inputs that were used in tasks that produced good output. So imagine that tasks 101 to 110 were run, and only 105 is bad. To re-run the data from 105 in a new pipeline instance, you'd set `reprocessingTasksExclude="101, 102, 103, 104, 106, 107, 108, 109, 110"`.
+
+In my experience, we haven't had to do this very often, but we have had to do it. So now you can, too!
+
+The default for `reprocessingTasksExclude` is empty (i.e., don't exclude anything).
+
+#### maxAutoResubmits
+
+It will sometimes happen that a Ziggy task will finish in such a way that you actually want to resubmit the task to the processing system without skipping a beat. There are a couple of circumstances where this may happen:
+
+- If you're uncertain of the settings you've put in for the amount of wall time your task should request, then it's possible that the task will reach its wall time limit before all the subtasks are processed. In this case, you would simply want to resubmit the task so that the remaining subtasks (or at least some of them) complete.
+- If you're using an algorithm that has non-deterministic errors, the result will be that subtasks will "fail," but if the subtasks are re-run they will (probably) complete successfully. In this case, you would want to resubmit to get the handful of "failed" subtasks completed.
+
+Because of issues like these, Ziggy has the capacity to automatically resubmit a task if it has incomplete or failed subtasks. The user can limit the number of automatic resubmits that the task gets; this prevents you from getting into an infinite loop of resubmits if something more serious is going wrong. the `maxAutoResubmits` parameter tells Ziggy how many times it may resubmit a task without your involvement before it gives up and waits for you to come along and see what's broken.
+
+Note that this is a potentially dangerous option to use! The problem is that Ziggy can't tell the difference between a failure that a typical user would want to automatically resubmit, and a failure that is a real problem that needs human intervention (for example, a bug in the algorithm software). If the situation is in the second category, you can wind up with Ziggy resubmitting a task that's simply going to fail in exactly the same way as it did the first time it was submitted.
+
+The default for `maxAutoResubmits` is zero.
diff --git a/doc/user-manual/troubleshooting.md b/doc/user-manual/troubleshooting.md
new file mode 100644
index 0000000..d022ac9
--- /dev/null
+++ b/doc/user-manual/troubleshooting.md
@@ -0,0 +1,32 @@
+## Troubleshooting Pipeline Execution
+
+It's been said that the only things in life that are guaranteed are death and taxes, but software failures should probably be on that list as well. When you start setting up your own pipelines, sure as sugar you're going to encounter problems! Here's where we get started helping you figure out where it went wrong, why it went wrong, and how to get back on track.
+
+### Creating an "Exception" in the Sample Pipeline
+
+On the console, go back to the `Configuration` tab, select `Parameter Library`, and double-click `Algorithm Parameters`. You'll see that there's a boolean parameter, `throw exception subtask_0`. Check the check box and save the parameter set.
+
+What this does is to tell the permuter's Python-side "glue" code that it should deliberately throw an exception during the processing of subtask zero. Obviously you shouldn't put such a parameter into your own pipeline! But in this case it's useful because it allows us to generate an exception in a controlled manner and watch what happens.
+
+Now go to the `Operations` tab, to its Triggers tab, and set up to run `permuter` and `flip` nodes in the sample pipeline. Launch the pipeline and return to the `Instances` tab. After a few seconds you'll see something like this:
+
+![](images/exception-1.png)
+
+What you see is that at the moment when I took my screen shot, 2 subtasks had completed and 1 had failed in each task, which is why each has a `P-state` that includes `(4 / 2 / 1)` (i.e., total subtasks, completed subtasks, failed subtasks). After a little more time has passed, you'll see this:
+
+![](images/exception-2.png)
+
+What indications do we have that all is not well? Let me recount the signs and portents:
+
+- The instance state is `ERRORS_STALLED`, which means that Ziggy can't move on to the next pipeline node due to errors.
+- The task state is `ERROR`.
+- The task P-states are `Ac` (algorithm complete), and subtask counts are 4 / 3 / 1, so 1 failed subtask per task, as expected.
+- Both the `Pi` stoplight (pipelines) and the `A` stoplight (alerts) are red.
+
+### [Log Files](log-files.md)
+
+The first stop for understanding what went wrong is the log files. Ziggy produces a lot of these!
+
+### [Using the Ziggy GUI for Troubleshooting](ziggy-gui-troubleshootihng.md)
+
+In most cases, you wan't want or need to resort to manually pawing through log files. In most cases, you can use Ziggy for troubleshooting and to respond to problems.
diff --git a/doc/user-manual/user-manual.md b/doc/user-manual/user-manual.md
new file mode 100644
index 0000000..5ad4e17
--- /dev/null
+++ b/doc/user-manual/user-manual.md
@@ -0,0 +1,77 @@
+
+
+# Welcome to Ziggy!
+
+Ziggy is "A Pipeline management system for Data Analysis Pipelines." This is the user guide and assorted documentation.
+
+## Table of Contents
+
+1. [Super-Basic Introduction](../../README.md)
+
+2. [Prerequisites and System Requirements](system-requirements.md)
+
+3. [Pipeline Architecture](pipeline-architecture.md)
+
+4. [Downloading and Building Ziggy](downloading-and-building-ziggy.md)
+
+5. [Configuring a Pipeline](configuring-pipeline.md)
+
+ 5.1. [Module Parameters](module-parameters.md)
+
+ 5.2. [Data File Types](data-file-types.md)
+
+ 5.3. [Pipeline Definition](pipeline-definition.md)
+
+6. [Building a Pipeline](building-pipeline.md)
+
+7. [Setting up the Relational Database Management System (RDBMS)](rdbms.md)
+
+8. [Running the Cluster](running-pipeline.md)
+
+9. [Using the Ziggy Console, Part 1](ziggy-gui.md)
+
+ 9.1. [Starting a Pipeline](start-pipeline.md)
+
+ 9.2. [The Instances Panel](instances-panel.md)
+
+ 9.3. [Changing the Start and End Nodes](start-end-nodes.md)
+
+ 9.4. [Changing Module Parameter Values](change-param-values.md)
+
+10. [Intermediate Topics](intermediate-topics.md)
+
+ 10.1. [The Datastore and the Task Directory](datastore-task-dir.md)
+
+ 10.2. [The Task Configuration Parameter Sets](task-configuration.md)
+
+ 10.3. [The Properties File](properties.md)
+
+11. [Troubleshooting Pipeline Execution](troubleshooting.md)
+
+ 11.1. [Log Files in Ziggy](log-files.md)
+
+12. [Using the Ziggy Console, Part 2: Troubleshooting](ziggy-gui-troubleshooting.md)
+
+ 12.1. [Monitoring Tab](monitoring.md)
+
+ 12.2. [Alerts Panel](alerts.md)
+
+ 12.3. [Log Files Display](display-logs.md)
+
+ 12.4. [Re-Run or Resume a Failed Task](rerun-task.md)
+
+13. [Advanced Topics](advanced-topics.md)
+
+ 13.1. [High Performance Computing Overview](select-hpc.md)
+
+ 13.2. [The Remote Execution Dialog Box](remote-dialog.md)
+
+ 13.3. [HPC Cost Estimation](hpc-cost.md)
+
+ 13.4. [Deleting Tasks](delete-tasks.md)
+
+ 13.5. [Data Receipt Execution Flow](data-receipt.md)
+
+ 13.6. [Data Receipt Display](data-receipt-display.md)
+
+14. Contact Us
diff --git a/doc/user-manual/ziggy-gui-troubleshooting.md b/doc/user-manual/ziggy-gui-troubleshooting.md
new file mode 100644
index 0000000..496d249
--- /dev/null
+++ b/doc/user-manual/ziggy-gui-troubleshooting.md
@@ -0,0 +1,19 @@
+## Using the Ziggy Console, Part 2: Troubleshooting
+
+Here are some articles on things you can use the Ziggy console for that relate to troubleshooting and recovery from problems.
+
+### [Monitoring Tab](monitoring.md)
+
+There's a whole bunch of stuff you can see on the Monitoring tab.
+
+### [Alerts Panel](alerts.md)
+
+When things happen that the operator should know about, they show up at the `Alerts` panel.
+
+### [Log Files Display](display-logs.md)
+
+For any given task you can display the task's log files directly from the console.
+
+### [Re-Run or Resume a Failed Task](rerun-task.md)
+
+Ziggy also gives you options when you've decided what you want to do about a failed task.
diff --git a/doc/user-manual/ziggy-gui.md b/doc/user-manual/ziggy-gui.md
new file mode 100644
index 0000000..4b681ca
--- /dev/null
+++ b/doc/user-manual/ziggy-gui.md
@@ -0,0 +1,22 @@
+## Using the Ziggy Console, Part 1
+
+I ain't gonna lie to you: getting a full understanding of the Ziggy console is a tall order. For this reason, we've broken it down into several parts, and then broken those parts down into even smaller parts. Here in Part 1, we'll concentrate on things that are relevant to starting and running a pipeline under relatively normal circumstances.
+
+As we go through it, we'll always use the sample pipeline to show what we're talking about in the most concrete way we can.
+
+### [Start a Pipeline](start-pipeline.md)
+
+This is probably the first thing you want to do: see an actual pipeline run!
+
+### [The Instances Panel](instances-panel.md)
+
+Watch what Ziggyy is doing in real time.
+
+### [Change the Start and End Nodes](start-end-nodes.md)
+
+There's no law that says you have to run the pipeline straight through from its first node to its last.
+
+### [Change Module Parameter Values](change-param-values.md)
+
+How the user adjusts the user-adjustable parameters.
+
diff --git a/etc/log4j-matlab-interactive.xml b/etc/log4j-matlab-interactive.xml
new file mode 100644
index 0000000..60ccd99
--- /dev/null
+++ b/etc/log4j-matlab-interactive.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/log4j2.xml b/etc/log4j2.xml
new file mode 100644
index 0000000..84f3700
--- /dev/null
+++ b/etc/log4j2.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ %d %-5p [%t:%C{1}.%M] %X{logStreamIdentifier} %m%n
+ ${sys:cli.logfile:-/dev/null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etc/pipeline.properties.EXAMPLE b/etc/pipeline.properties.EXAMPLE
new file mode 100644
index 0000000..b9259b9
--- /dev/null
+++ b/etc/pipeline.properties.EXAMPLE
@@ -0,0 +1,32 @@
+# See the Properties File article in the user manual for descriptions
+# of these properties and especially for properties not listed.
+
+data.receipt.dir = ${pipeline.results.dir}/data-receipt
+database.connections = 100
+database.host = localhost
+database.name = majortom
+database.port = 5432
+database.software.name = postgresql
+datastore.root.dir = ${pipeline.results.dir}/datastore
+hibernate.connection.password =
+hibernate.connection.username = ${env:USER}
+moduleExe.memdrone.enabled = true
+moduleExe.memdrone.sleepSeconds = 10
+moduleExe.useSymlinks = true
+pi.worker.heapSize = 12000
+pi.worker.moduleExe.binPath = ${env:JAVA_HOME}/bin:/bin:/usr/bin
+pi.worker.moduleExe.mcrRoot = ${env:MATLAB_HOME}
+pi.worker.threadCount = 2
+pipeline.classpath = "${pipeline.home.dir}/libs/*"
+pipeline.definition.dir = ${pipeline.root.dir}/sample-pipeline/config
+pipeline.home.dir = ${pipeline.root.dir}/sample-pipeline/build
+pipeline.logoFile = ${pipeline.definition.dir}/pipeline-logo.png
+pipeline.results.dir = ${pipeline.root.dir}/pipeline-results
+pipeline.root.dir = /path/to/pipeline/directories
+remote.cluster.name = nas
+remote.group = 1234
+remote.host = host1.example.com,host2.example.come
+remote.user = user
+rmi.registry.port = 1099
+ziggy.config.path = ${ziggy.home.dir}/etc/ziggy.properties
+ziggy.home.dir = ${pipeline.root.dir}/ziggy/build
diff --git a/etc/runjava.conf b/etc/runjava.conf
new file mode 100644
index 0000000..7ffab19
--- /dev/null
+++ b/etc/runjava.conf
@@ -0,0 +1,46 @@
+# Runjava configuration file
+#
+# FORMAT:
+#
+# [DEFAULT_JVM_ARGS|LOGFILE_PREFIX]=value
+# nickname|fully.specified.classname|space-delimited JVM args|space-delimited program args
+
+# This is the runjava configuration file for Ziggy.
+#
+# The LOGFILE_PREFIX causes the logging from subsequent nicknames to
+# flow into a particular file in the pipeline results logs/cli
+# directory. For example, all the nicknames below
+# LOGFILE_PREFIX=runjava will log to logs/cli/runjava.log; the one
+# under LOGFILE_PREFIX=cluster logs to logs/cli/cluster.log and so on.
+# You can use this mechanism to divert logging for your Java classes
+# to specific files in logs/cli so that you can find your log messages
+# more easily after running.
+
+# These are the runjava defaults, which can be overridden.
+DEFAULT_JVM_ARGS=-Dlog4j2.configurationFile=${ziggy.home.dir}/etc/log4j2.xml -Djava.library.path=${ziggy.home.dir}/lib
+
+compute-node-master|gov.nasa.ziggy.module.ComputeNodeMaster|-XX:ParallelGCThreads=2 -XX:+UseParallelGC -XX:OnOutOfMemoryError="kill -QUIT %p"|
+
+LOGFILE_PREFIX=cluster
+cluster|gov.nasa.ziggy.ui.ClusterController||
+
+LOGFILE_PREFIX=console
+console|gov.nasa.ziggy.ui.ZiggyConsole|-Dsun.java2d.xrender=false -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Xmx2G -Dlog4j.configuration=file:${pipeline.home.dir}/etc/log4j-console-rolling-file.xml|
+
+LOGFILE_PREFIX=runjava
+dump-err|gov.nasa.ziggy.module.io.DumpMatlabErrCli||
+dump-props|gov.nasa.ziggy.services.configuration.DumpSystemProperties||
+dump-system-properties|gov.nasa.ziggy.services.config.DumpSystemProperties|-Drunjava.env.ziggy.home.dir=%ZIGGY_HOME%|
+execsql|gov.nasa.ziggy.dbservice.SqlRunner||
+metrics|gov.nasa.ziggy.metrics.report.MetricsCli||
+pd-export|gov.nasa.ziggy.pipeline.definition.PipelineDefinitionCli||-export
+pd-import|gov.nasa.ziggy.pipeline.definition.PipelineDefinitionCli||-import
+pe-import|gov.nasa.ziggy.services.events.ZiggyEventHandlerDefinitionFileImporter||
+perf-report|gov.nasa.ziggy.metrics.report.PerformanceReport||
+pl-export|gov.nasa.ziggy.parameters.ParameterLibraryImportExportCli||-export
+pl-import|gov.nasa.ziggy.parameters.ParameterLibraryImportExportCli||-import
+pl-override|gov.nasa.ziggy.parameters.ParameterLibraryOverrideCli||
+pt-import|gov.nasa.ziggy.data.management.DataFileTypeImporter||
+spoc-version|gov.nasa.ziggy.common.version.ZiggyVersionCli||
+status-listener|gov.nasa.ziggy.services.process.StatusMessageLogger||
+generate-manifest|gov.nasa.ziggy.data.management.Manifest||
diff --git a/etc/worker.wrapper.conf b/etc/worker.wrapper.conf
new file mode 100644
index 0000000..eb7c7c9
--- /dev/null
+++ b/etc/worker.wrapper.conf
@@ -0,0 +1,72 @@
+#********************************************************************
+#
+# Wrapper Properties for Worker
+#
+#********************************************************************
+
+# main() entry point
+# ClusterController.workerCommand() adds additional parameters starting at 2.
+wrapper.app.parameter.1=gov.nasa.ziggy.worker.WorkerPipelineProcess
+
+# Title to use when running as a console
+wrapper.console.title=Ziggy Worker
+
+# Initial Java Heap Size (in MB)
+wrapper.java.initmemory=512
+
+# Additional Java parameters.
+# ClusterController.workerCommand() adds additional parameters starting at 5.
+wrapper.java.additional.1=-Dlog4j2.configurationFile=etc/log4j2.xml
+wrapper.java.additional.2=-Dlog4j.logfile.prefix=logs/worker
+wrapper.java.additional.3=-XX:+UseCompressedOops
+wrapper.java.additional.4=-XX:-OmitStackTraceInFastThrow
+
+# Disable timeouts because the workers are getting killed when
+# MATLAB processes are consuming 100% CPU for extended periods of time
+wrapper.ping.timeout=0
+
+# Java Application
+wrapper.java.command=java
+
+# Java Main class. This class must implement the WrapperListener interface
+# or guarantee that the WrapperManager class is initialized. Helper
+# classes are provided to do this for you. See the Integration section
+# of the documentation for details.
+wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
+
+wrapper.working.dir=../
+
+# limit the # of wrapper logfiles
+wrapper.logfile.maxfiles=4
+wrapper.logfile.rollmode=SIZE
+wrapper.logfile.maxsize=8192k
+
+#********************************************************************
+# Wrapper Logging Properties
+#********************************************************************
+# Format of output for the console. (See docs for formats)
+wrapper.console.format=PM
+
+# Log Level for console output. (See docs for log levels)
+wrapper.console.loglevel=INFO
+
+# Format of output for the log file. (See docs for formats)
+wrapper.logfile.format=LPTM
+
+# Log Level for log file output. (See docs for log levels)
+wrapper.logfile.loglevel=INFO
+
+# Maximum size that the log file will be allowed to grow to before
+# the log is rolled. Size is specified in bytes. The default value
+# of 0, disables log rolling. May abbreviate with the 'k' (kb) or
+# 'm' (mb) suffix. For example: 10m = 10 megabytes.
+wrapper.logfile.maxsize=8192k
+
+# Maximum number of rolled log files which will be allowed before old
+# files are deleted. The default value of 0 implies no limit.
+wrapper.logfile.maxfiles=8
+
+# Log Level for sys/event log output. (See docs for log levels)
+wrapper.syslog.loglevel=NONE
+
+
diff --git a/etc/ziggy.properties b/etc/ziggy.properties
new file mode 100644
index 0000000..7238775
--- /dev/null
+++ b/etc/ziggy.properties
@@ -0,0 +1,37 @@
+#
+# Properties file for Ziggy processes.
+#
+# Lookup order for properties is as follows:
+# 1. System properties (System.getProperty())
+# 2. Pipeline properties file
+# 3. Ziggy properties file (this file)
+
+hibernate.c3p0.idle_test_period = 100
+hibernate.c3p0.max_size = 115
+hibernate.c3p0.max_statements = 50
+hibernate.c3p0.min_size = 1
+hibernate.c3p0.timeout = 300
+hibernate.connection.url = jdbc:${database.software.name}://${database.host}:${database.port}/${database.name}
+hibernate.format_sql = true
+hibernate.id.new_generator_mappings = true
+# Increased batch size from 0 to 30 to drop collateral pixel table ingest time from 10 minutes to 2 minutes.
+hibernate.jdbc.batch_size = 30
+hibernate.show_sql = false
+hibernate.use_sql_comments = false
+
+matlab.log4j.config = ${ziggy.home.dir}/etc/log4j-matlab-interactive.xml
+matlab.log4j.initialize = true
+
+pi.metrics.reaper.checkIntervalMins = 5
+pi.metrics.reaper.maxRows = 10000
+
+pi.worker.moduleExe.saveMatFiles = true
+pi.worker.taskMetrics.Algorithm = pipeline.module.executeAlgorithm.matlab.controller.execTime
+pi.worker.taskMetrics.Commit = pipeline.module.commitTime
+pi.worker.taskMetrics.Filestore = fs.client.
+pi.worker.taskMetrics.Framework = pipeline.module.executeAlgorithm.java.serializationTime,pipeline.module.executeAlgorithm.matlab.serializationTime
+pi.worker.taskMetrics.MatIO = pipeline.module.executeAlgorithm.matlab.readWriteMatfilesTime
+pi.worker.taskMetrics.categories = Filestore,Commit,Framework,Algorithm,MatIO
+
+# Used by ConfigurationServiceTest
+test.file.property = from.default.location
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..af413ad
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,10 @@
+buildType = release
+org.gradle.color.error = RED
+org.gradle.daemon = true
+org.gradle.jvmargs = -Xms256m -Xmx2048m
+org.gradle.parallel = false
+
+# Property for ziggy-dependencies directory relative to ziggy or $rootDir.
+# Because $rootDir can't be used in this context, this property is prefaced
+# by $rootDir in build.gradle.
+ziggyDependencies = buildSrc/build
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..99340b4
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2c2bbe5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/ide/ziggy-eclipse-clean-up.xml b/ide/ziggy-eclipse-clean-up.xml
new file mode 100644
index 0000000..21c05b1
--- /dev/null
+++ b/ide/ziggy-eclipse-clean-up.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/ziggy-eclipse-formatter.xml b/ide/ziggy-eclipse-formatter.xml
new file mode 100644
index 0000000..9b53d5a
--- /dev/null
+++ b/ide/ziggy-eclipse-formatter.xml
@@ -0,0 +1,365 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/licenses/HDF5_110121.PDF b/licenses/HDF5_110121.PDF
new file mode 100644
index 0000000..c581551
Binary files /dev/null and b/licenses/HDF5_110121.PDF differ
diff --git a/licenses/com.github.librepdf--openpdf--1.3.x b/licenses/com.github.librepdf--openpdf--1.3.x
new file mode 100644
index 0000000..ecade02
--- /dev/null
+++ b/licenses/com.github.librepdf--openpdf--1.3.x
@@ -0,0 +1,326 @@
+*** ***
+*** Dual License - See also MPL below. ***
+*** ***
+
+GNU LESSER GENERAL PUBLIC LICENSE
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+Preamble
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
+
+We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+a) The modified work must itself be a software library.
+b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
+c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
+d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
+(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
+
+However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
+
+When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
+b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
+c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
+d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
+e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
+For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
+b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+How to Apply These Terms to Your New Libraries
+If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).
+
+To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
+
+one line to give the library's name and an idea of what it does.
+Copyright (C) year name of author
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in
+the library `Frob' (a library for tweaking knobs) written
+by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+Ty Coon, President of Vice
+That's all there is to it!
+
+
+Mozilla Public License
+Version 2.0
+1. Definitions
+1.1. “Contributor”
+means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.
+
+1.2. “Contributor Version”
+means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.
+
+1.3. “Contribution”
+means Covered Software of a particular Contributor.
+
+1.4. “Covered Software”
+means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.
+
+1.5. “Incompatible With Secondary Licenses”
+means
+
+that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or
+
+that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.
+
+1.6. “Executable Form”
+means any form of the work other than Source Code Form.
+
+1.7. “Larger Work”
+means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.
+
+1.8. “License”
+means this document.
+
+1.9. “Licensable”
+means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.
+
+1.10. “Modifications”
+means any of the following:
+
+any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or
+
+any new file in Source Code Form that contains any Covered Software.
+
+1.11. “Patent Claims” of a Contributor
+means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.
+
+1.12. “Secondary License”
+means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.
+
+1.13. “Source Code Form”
+means the form of the work preferred for making modifications.
+
+1.14. “You” (or “Your”)
+means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. License Grants and Conditions
+2.1. Grants
+Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and
+
+under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.
+
+2.2. Effective Date
+The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:
+
+for any code that a Contributor has removed from Covered Software; or
+
+for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or
+
+under Patent Claims infringed by Covered Software in the absence of its Contributions.
+
+This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).
+
+2.5. Representation
+Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.
+
+2.7. Conditions
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.
+
+3. Responsibilities
+3.1. Distribution of Source Form
+All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.
+
+3.2. Distribution of Executable Form
+If You distribute Covered Software in Executable Form then:
+
+such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and
+
+You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).
+
+3.4. Notices
+You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.
+
+5. Termination
+5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.
+
+6. Disclaimer of Warranty
+Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.
+
+7. Limitation of Liability
+Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
+
+8. Litigation
+Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.
+
+9. Miscellaneous
+This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+10.1. New Versions
+Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.
+
+10.2. Effect of New Versions
+You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.
+
+10.3. Modified Versions
+If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.
diff --git a/licenses/com.github.testdriven.guice--commons-configuration--1.x b/licenses/com.github.testdriven.guice--commons-configuration--1.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/com.github.testdriven.guice--commons-configuration--1.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/com.google.guava--guava--23.x b/licenses/com.google.guava--guava--23.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/com.google.guava--guava--23.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/com.jgoodies--jgoodies-forms--1.9.x b/licenses/com.jgoodies--jgoodies-forms--1.9.x
new file mode 100644
index 0000000..516f77e
--- /dev/null
+++ b/licenses/com.jgoodies--jgoodies-forms--1.9.x
@@ -0,0 +1,9 @@
+Copyright
+
+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 HOLDER 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.
diff --git a/licenses/com.jgoodies--jgoodies-looks--2.7.x b/licenses/com.jgoodies--jgoodies-looks--2.7.x
new file mode 100644
index 0000000..516f77e
--- /dev/null
+++ b/licenses/com.jgoodies--jgoodies-looks--2.7.x
@@ -0,0 +1,9 @@
+Copyright
+
+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 HOLDER 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.
diff --git a/licenses/commons-cli--commons-cli--1.5.x b/licenses/commons-cli--commons-cli--1.5.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/commons-cli--commons-cli--1.5.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/commons-codec--commons-codec--1.x b/licenses/commons-codec--commons-codec--1.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/commons-codec--commons-codec--1.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/commons-io--commons-io--2.11.x b/licenses/commons-io--commons-io--2.11.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/commons-io--commons-io--2.11.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/git-2.x b/licenses/git-2.x
new file mode 100644
index 0000000..536e555
--- /dev/null
+++ b/licenses/git-2.x
@@ -0,0 +1,360 @@
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ HOWEVER, in order to allow a migration to GPLv3 if that seems like
+ a good idea, I also ask that people involved with the project make
+ their preferences known. In particular, if you trust me to make that
+ decision, you might note so in your copyright message, ie something
+ like
+
+ This file is licensed under the GPL v2, or a later version
+ at the discretion of Linus.
+
+ might avoid issues. But we can also just decide to synchronize and
+ contact all copyright holders on record if/when the occasion arises.
+
+ Linus Torvalds
+
+----------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/licenses/gradle-4.1.x b/licenses/gradle-4.1.x
new file mode 100644
index 0000000..f013fd5
--- /dev/null
+++ b/licenses/gradle-4.1.x
@@ -0,0 +1,420 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+
+==============================================================================
+Licenses for included components:
+
+------------------------------------------------------------------------------
+Eclipse Public License 1.0
+https://opensource.org/licenses/EPL-1.0
+
+junit:junit
+org.sonatype.aether:aether-api
+org.sonatype.aether:aether-connector-wagon
+org.sonatype.aether:aether-impl
+org.sonatype.aether:aether-spi
+org.sonatype.aether:aether-util
+
+------------------------------------------------------------------------------
+3-Clause BSD
+https://opensource.org/licenses/BSD-3-Clause
+
+com.google.code.findbugs:jsr305
+
+org.hamcrest:hamcrest-core
+BSD License
+
+Copyright (c) 2000-2015 www.hamcrest.org
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of
+conditions and the following disclaimer. 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.
+
+Neither the name of Hamcrest nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written
+permission.
+
+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 OWNER 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.
+
+com.esotericsoftware.kryo:kryo
+com.esotericsoftware.minlog:minlog
+Copyright (c) 2008-2018, Nathan Sweet All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+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.
+Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+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 HOLDER 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.
+
+org.ow2.asm:asm
+org.ow2.asm:asm-analysis
+org.ow2.asm:asm-commons
+org.ow2.asm:asm-tree
+org.ow2.asm:asm-util
+ASM: a very small and fast Java bytecode manipulation framework
+ Copyright (c) 2000-2011 INRIA, France Telecom
+ All rights reserved.
+
+ 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.
+ 3. Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ 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 OWNER 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.
+
+------------------------------------------------------------------------------
+MIT
+
+com.googlecode.plist:dd-plist
+dd-plist - An open source library to parse and generate property lists
+Copyright (C) 2016 Daniel Dreibrodt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+org.bouncycastle:bcpg-jdk15on
+org.bouncycastle:bcprov-jdk15on
+Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+org.slf4j:jcl-over-slf4j
+org.slf4j:jul-to-slf4j
+org.slf4j:log4j-over-slf4j
+org.slf4j:slf4j-api
+ Copyright (c) 2004-2017 QOS.ch
+ All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+------------------------------------------------------------------------------
+CDDL
+https://opensource.org/licenses/CDDL-1.0
+
+com.sun.xml.bind:jaxb-impl
+
+------------------------------------------------------------------------------
+LGPL 2.1
+https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+
+org.samba.jcifs:jcifs
+
+org.jetbrains.intellij.deps:trove4j
+
+------------------------------------------------------------------------------
+License for the GNU Trove library included by the Kotlin embeddable compiler
+------------------------------------------------------------------------------
+The source code for GNU Trove is licensed under the Lesser GNU Public License (LGPL).
+
+ Copyright (c) 2001, Eric D. Friedman All Rights Reserved. This library is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+Two classes (HashFunctions and PrimeFinder) included in Trove are licensed under the following terms:
+
+ Copyright (c) 1999 CERN - European Organization for Nuclear Research. Permission to use, copy, modify, distribute and sell this software
+ and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and
+ that both that copyright notice and this permission notice appear in supporting documentation. CERN makes no representations about the
+ suitability of this software for any purpose. It is provided "as is" without expressed or implied warranty.
+
+The source code of modified GNU Trove library is available at
+ https://github.com/JetBrains/intellij-deps-trove4j (with trove4j_changes.txt describing the changes)
+
+------------------------------------------------------------------------------
+Eclipse Distribution License 1.0
+https://www.eclipse.org/org/documents/edl-v10.php
+
+org.eclipse.jgit:org.eclipse.jgit
+
+------------------------------------------------------------------------------
+BSD-style
+
+com.jcraft:jsch
+com.jcraft:jzlib
+
+Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved.
+
+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.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 JCRAFT,
+INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE 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.
+
+------------------------------------------------------------------------------
+Eclipse Public License 2.0
+https://www.eclipse.org/legal/epl-2.0/
+
+org.junit.platform:junit-platform-launcher
+
+------------------------------------------------------------------------------
+Mozilla Public License 2.0
+https://www.mozilla.org/en-US/MPL/2.0/
+
+org.mozilla:rhino
diff --git a/licenses/junit--junit--4.x b/licenses/junit--junit--4.x
new file mode 100644
index 0000000..b0476ee
--- /dev/null
+++ b/licenses/junit--junit--4.x
@@ -0,0 +1,86 @@
+Eclipse Public License - v 1.0
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
diff --git a/licenses/licenses.md b/licenses/licenses.md
new file mode 100644
index 0000000..08a1bfa
--- /dev/null
+++ b/licenses/licenses.md
@@ -0,0 +1,49 @@
+
+
+# Licenses
+
+This file lists the third-party software that is used by Ziggy. Links to the software's home page, documentation, and license, the requirement that the software fulfills if applicable, and an identified alternative are provided per NPR 7150.2, SWE-027, and the Ziggy Software Management Plan (SMP).
+
+## Tools
+
+|Name|Requirement|Alternative|
+|---|---|---|---|---|
+|[Git](https://git-scm.com/) [(docs)](https://git-scm.com/doc) [(license)](git-2.x)|SWE-42|Subversion|
+|[Gradle](https://gradle.org/) [(docs)](https://docs.gradle.org/current/userguide/userguide.html) [(license)](gradle-4.1.x)||ant|
+
+## Libraries
+
+|Name|Requirement|Alternative|
+|---|---|---|
+|[commons-cli](https://commons.apache.org/proper/commons-cli/) [(docs)](https://commons.apache.org/proper/commons-cli/introduction.html) [(license)](commons-cli--commons-cli--1.5.x)|||
+|[commons-codec](https://commons.apache.org/proper/commons-codec/) [(docs)](https://commons.apache.org/proper/commons-codec/userguide.html) [(license)](commons-codec--commons-codec--1.x)|||
+|[commons-collections4](https://commons.apache.org/proper/commons-collections/) [(docs)](https://commons.apache.org/proper/commons-collections/userguide.html) [(license)](org.apache.commons--commons-collections4--4.x)|||
+|[commons-compress](https://commons.apache.org/proper/commons-compress/) [(docs)](https://commons.apache.org/proper/commons-compress/examples.html) [(license)](org.apache.commons--commons-compress--1.x)|||
+|[commons-configuration](https://commons.apache.org/proper/commons-configuration/) [(docs)](https://commons.apache.org/proper/commons-configuration/userguide_v1.10/user_guide.html) [(license)](com.github.testdriven.guice--commons-configuration--1.x)|||
+|[commons-csv](https://commons.apache.org/proper/commons-csv/) [(docs)](https://commons.apache.org/proper/commons-csv/user-guide.html) [(license)](org.apache.commons--commons-csv--1.9.x)|||
+|[commons-exec](https://commons.apache.org/proper/commons-exec/) [(docs)](https://commons.apache.org/proper/commons-exec/tutorial.html) [(license)](org.apache.commons--commons-exec--1.x)|||
+|[commons-io](https://commons.apache.org/proper/commons-io/) [(docs)](https://commons.apache.org/proper/commons-io/description.html) [(license)](commons-io--commons-io--2.11.x)|||
+|[commons-lang3](https://commons.apache.org/proper/commons-lang/) [(docs)](https://commons.apache.org/proper/commons-lang/javadocs/api-release/index.html) [(license)](org.apache.commons--commons-lang3--3.12.x)|||
+|[commons-math3](https://commons.apache.org/proper/commons-math/) [(docs)](https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/index.html) [(license)](org.apache.commons--commons-math3--3.6.x)|||
+|[commons-text](https://commons.apache.org/proper/commons-text/) [(docs)](https://commons.apache.org/proper/commons-text/userguide.html) [(license)](org.apache.commons--commons-text--1.x)|||
+|[freemarker](https://freemarker.apache.org/index.html) [(docs)](https://freemarker.apache.org/docs/index.html) [(license)](org.freemarker--freemarker--2.3.x)|||
+|[guava](https://github.com/google/guava) [(docs)](https://guava.dev/releases/23.6.1-jre/api/docs/) [(license)](com.google.guava--guava--23.x)|||
+|[hamcrest](https://hamcrest.org/JavaHamcrest/) [(docs)](https://hamcrest.org/JavaHamcrest/) [(license)](org.hamcrest--hamcrest--2.x)|||
+|[hdf5](https://www.hdfgroup.org/) [(docs)](https://portal.hdfgroup.org/display/HDF5/HDF5) [(license)](HDF5_110121.PDF)|||
+|[hibernate-core](https://hibernate.org/orm/) [(docs)](https://hibernate.org/orm/documentation/4.2/) [(license)](org.hibernate--hibernate-core--4.2.x)|||
+|[hibernate-jpa-2.0-api](https://hibernate.org/orm/) [(docs)](https://hibernate.org/orm/documentation/4.2/) [(license)](org.hibernate.javax.persistence--hibernate-jpa-2.0-api--1.0.x)|||
+|[hsqldb](https://hsqldb.org/) [(docs)](https://hsqldb.org/web/hsqlDocsFrame.html) [(license)](org.hsqldb--hsqldb--2.3.2)|||
+|[javassist](https://www.javassist.org/) [(docs)](https://www.javassist.org/) [(license)](org.javassist--javassist--3.18.1-GA)|||
+|[jfreechart](https://www.jfree.org/jfreechart/) [(docs)](https://www.jfree.org/jfreechart/javadoc/index.html) [(license)](org.jfree--jfreechart--1.0.x)|||
+|[jgoodies-forms](https://www.jgoodies.com/freeware/libraries/forms/) [(docs)](https://javadoc.io/doc/com.jgoodies/jgoodies-forms/latest/index.html) [(license)](com.jgoodies--jgoodies-forms--1.9.x)|||
+|[jgoodies-looks](https://www.jgoodies.com/freeware/libraries/looks/) [(docs)](https://javadoc.io/doc/com.jgoodies/jgoodies-looks/latest/index.html) [(license)](com.jgoodies--jgoodies-looks--2.7.x)|||
+|[junit](https://junit.org/junit4/) [(docs)](https://junit.org/junit4/javadoc/latest/index.html) [(license)](junit--junit--4.x)|SWE-062|TestNG|
+|[l2fprod-properties-editor](https://bitbucket.org/trosorg/l2fprod-properties-editor/src/master/) [(docs)](https://bitbucket.org/trosorg/l2fprod-properties-editor/src/master/) [(license)](org.tros--l2fprod-properties-editor--1.3.x)|||
+|[log4j-api](https://logging.apache.org/log4j/2.x/index.html) [(docs)](https://logging.apache.org/log4j/2.x/manual/index.html) [(license)](org.apache.logging.log4j--log4j-api--2.17.x)|||
+|[log4j-core](https://logging.apache.org/log4j/2.x/index.html) [(docs)](https://logging.apache.org/log4j/2.x/manual/index.html) [(license)](org.apache.logging.log4j--log4j-core--2.17.x)|||
+|[log4j-slf4j-impl](https://logging.apache.org/log4j/2.x/index.html) [(docs)](https://logging.apache.org/log4j/2.x/manual/index.html) [(license)](org.apache.logging.log4j--log4j-slf4j-impl--2.17.x)|||
+|[mockito-core](https://site.mockito.org/) [(docs)](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html) [(license)](org.mockito--mockito-core--1.10.x)||JMock|
+|[openpdf](https://github.com/LibrePDF/OpenPDF) [(docs)](https://github.com/LibrePDF/OpenPDF) [(license)](com.github.librepdf--openpdf--1.3.x)|||
+|[org-netbeans-swing-outline](https://netbeans.apache.org/) [(docs)](https://netbeans.apache.org/) [(license)](org.netbeans.api--org-netbeans-swing-outline--x)|||
+|[postgresql](https://www.postgresql.org/) [(docs)](https://www.postgresql.org/docs/) [(license)](org.postgresql--postgresql--9.4-1201-jdbc4)|||
+|[wrapper](https://wrapper.tanukisoftware.com/doc/english/home.html) [(docs)](https://wrapper.tanukisoftware.com/doc/english/introduction.html) [(license)](tanukisoft--wrapper--3.2.x)|||
diff --git a/licenses/org.apache.commons--commons-collections4--4.x b/licenses/org.apache.commons--commons-collections4--4.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-collections4--4.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-compress--1.x b/licenses/org.apache.commons--commons-compress--1.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-compress--1.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-csv--1.9.x b/licenses/org.apache.commons--commons-csv--1.9.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-csv--1.9.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-exec--1.x b/licenses/org.apache.commons--commons-exec--1.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-exec--1.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-lang3--3.12.x b/licenses/org.apache.commons--commons-lang3--3.12.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-lang3--3.12.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-math3--3.6.x b/licenses/org.apache.commons--commons-math3--3.6.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-math3--3.6.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.commons--commons-text--1.x b/licenses/org.apache.commons--commons-text--1.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.apache.commons--commons-text--1.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.logging.log4j--log4j-api--2.17.x b/licenses/org.apache.logging.log4j--log4j-api--2.17.x
new file mode 100644
index 0000000..4c9ad98
--- /dev/null
+++ b/licenses/org.apache.logging.log4j--log4j-api--2.17.x
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.logging.log4j--log4j-core--2.17.x b/licenses/org.apache.logging.log4j--log4j-core--2.17.x
new file mode 100644
index 0000000..4c9ad98
--- /dev/null
+++ b/licenses/org.apache.logging.log4j--log4j-core--2.17.x
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.apache.logging.log4j--log4j-slf4j-impl--2.17.x b/licenses/org.apache.logging.log4j--log4j-slf4j-impl--2.17.x
new file mode 100644
index 0000000..4c9ad98
--- /dev/null
+++ b/licenses/org.apache.logging.log4j--log4j-slf4j-impl--2.17.x
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.freemarker--freemarker--2.3.x b/licenses/org.freemarker--freemarker--2.3.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.freemarker--freemarker--2.3.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.hamcrest--hamcrest--2.x b/licenses/org.hamcrest--hamcrest--2.x
new file mode 100644
index 0000000..c073943
--- /dev/null
+++ b/licenses/org.hamcrest--hamcrest--2.x
@@ -0,0 +1,11 @@
+Copyright
+
+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.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+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 HOLDER 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.
diff --git a/licenses/org.hibernate--hibernate-core--4.2.x b/licenses/org.hibernate--hibernate-core--4.2.x
new file mode 100644
index 0000000..5e4071d
--- /dev/null
+++ b/licenses/org.hibernate--hibernate-core--4.2.x
@@ -0,0 +1,169 @@
+GNU Lesser General Public License
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]
+
+Preamble
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
+
+We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
+
+However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
+
+When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
+
+b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
+
+c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
+
+d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
+
+e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+How to Apply These Terms to Your New Libraries
+If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).
+
+
+
+To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
+
+ Copyright (C)
+
+This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/licenses/org.hibernate.javax.persistence--hibernate-jpa-2.0-api--1.0.x b/licenses/org.hibernate.javax.persistence--hibernate-jpa-2.0-api--1.0.x
new file mode 100644
index 0000000..5e4071d
--- /dev/null
+++ b/licenses/org.hibernate.javax.persistence--hibernate-jpa-2.0-api--1.0.x
@@ -0,0 +1,169 @@
+GNU Lesser General Public License
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]
+
+Preamble
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
+
+We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
+
+However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
+
+When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
+
+b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
+
+c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
+
+d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
+
+e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+How to Apply These Terms to Your New Libraries
+If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).
+
+
+
+To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
+
+ Copyright (C)
+
+This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/licenses/org.hsqldb--hsqldb--2.3.2 b/licenses/org.hsqldb--hsqldb--2.3.2
new file mode 100644
index 0000000..72dc224
--- /dev/null
+++ b/licenses/org.hsqldb--hsqldb--2.3.2
@@ -0,0 +1,29 @@
+/* Copyright (c) 2001-2022, The HSQL Development Group
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 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.
+ *
+ * Neither the name of the HSQL Development Group nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * 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 HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
+ * 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.
+ */
diff --git a/licenses/org.javassist--javassist--3.18.1-GA b/licenses/org.javassist--javassist--3.18.1-GA
new file mode 100644
index 0000000..ae775d1
--- /dev/null
+++ b/licenses/org.javassist--javassist--3.18.1-GA
@@ -0,0 +1,414 @@
+*** ***
+*** Triple License - See also Apache and MPL below. ***
+*** ***
+
+GNU LESSER GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright © 2007 Free Software Foundation, Inc.
+
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
+
+0. Additional Definitions.
+As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
+
+“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
+
+An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
+
+A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
+
+The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
+
+The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
+
+1. Exception to Section 3 of the GNU GPL.
+You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
+
+2. Conveying Modified Versions.
+If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
+
+a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
+b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
+3. Object Code Incorporating Material from Library Header Files.
+The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
+
+a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
+b) Accompany the object code with a copy of the GNU GPL and this license document.
+4. Combined Works.
+You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
+
+a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
+b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
+c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
+d) Do one of the following:
+0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
+1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
+e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
+5. Combined Libraries.
+You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
+b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+6. Revised Versions of the GNU Lesser General Public License.
+The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
+Mozilla Public License
+Version 2.0
+1. Definitions
+1.1. “Contributor”
+means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.
+
+1.2. “Contributor Version”
+means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.
+
+1.3. “Contribution”
+means Covered Software of a particular Contributor.
+
+1.4. “Covered Software”
+means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.
+
+1.5. “Incompatible With Secondary Licenses”
+means
+
+that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or
+
+that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.
+
+1.6. “Executable Form”
+means any form of the work other than Source Code Form.
+
+1.7. “Larger Work”
+means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.
+
+1.8. “License”
+means this document.
+
+1.9. “Licensable”
+means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.
+
+1.10. “Modifications”
+means any of the following:
+
+any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or
+
+any new file in Source Code Form that contains any Covered Software.
+
+1.11. “Patent Claims” of a Contributor
+means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.
+
+1.12. “Secondary License”
+means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.
+
+1.13. “Source Code Form”
+means the form of the work preferred for making modifications.
+
+1.14. “You” (or “Your”)
+means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. License Grants and Conditions
+2.1. Grants
+Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and
+
+under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.
+
+2.2. Effective Date
+The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:
+
+for any code that a Contributor has removed from Covered Software; or
+
+for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or
+
+under Patent Claims infringed by Covered Software in the absence of its Contributions.
+
+This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).
+
+2.5. Representation
+Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.
+
+2.7. Conditions
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.
+
+3. Responsibilities
+3.1. Distribution of Source Form
+All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.
+
+3.2. Distribution of Executable Form
+If You distribute Covered Software in Executable Form then:
+
+such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and
+
+You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).
+
+3.4. Notices
+You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.
+
+5. Termination
+5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.
+
+6. Disclaimer of Warranty
+Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.
+
+7. Limitation of Liability
+Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
+
+8. Litigation
+Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.
+
+9. Miscellaneous
+This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+10.1. New Versions
+Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.
+
+10.2. Effect of New Versions
+You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.
+
+10.3. Modified Versions
+If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.
diff --git a/licenses/org.jfree--jfreechart--1.0.x b/licenses/org.jfree--jfreechart--1.0.x
new file mode 100644
index 0000000..f2f81d6
--- /dev/null
+++ b/licenses/org.jfree--jfreechart--1.0.x
@@ -0,0 +1,56 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright © 2007 Free Software Foundation, Inc.
+
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
+
+0. Additional Definitions.
+As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
+
+“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
+
+An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
+
+A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
+
+The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
+
+The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
+
+1. Exception to Section 3 of the GNU GPL.
+You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
+
+2. Conveying Modified Versions.
+If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
+
+a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
+b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
+3. Object Code Incorporating Material from Library Header Files.
+The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
+
+a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
+b) Accompany the object code with a copy of the GNU GPL and this license document.
+4. Combined Works.
+You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
+
+a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
+b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
+c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
+d) Do one of the following:
+0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
+1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
+e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
+5. Combined Libraries.
+You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
+b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+6. Revised Versions of the GNU Lesser General Public License.
+The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
diff --git a/licenses/org.mockito--mockito-core--1.10.x b/licenses/org.mockito--mockito-core--1.10.x
new file mode 100644
index 0000000..b02357a
--- /dev/null
+++ b/licenses/org.mockito--mockito-core--1.10.x
@@ -0,0 +1,9 @@
+The MIT License
+
+Copyright (c) 2007 Mockito contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/licenses/org.netbeans.api--org-netbeans-swing-outline--x b/licenses/org.netbeans.api--org-netbeans-swing-outline--x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.netbeans.api--org-netbeans-swing-outline--x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/org.postgresql--postgresql--9.4-1201-jdbc4 b/licenses/org.postgresql--postgresql--9.4-1201-jdbc4
new file mode 100644
index 0000000..53de8e5
--- /dev/null
+++ b/licenses/org.postgresql--postgresql--9.4-1201-jdbc4
@@ -0,0 +1,9 @@
+This is a template license. The body of the license starts at the end of this paragraph. To use it, say that it is The PostgreSQL License, and then substitute the copyright year and name of the copyright holder into the body of the license. Then put the license into a prominent file ("COPYRIGHT", "LICENSE" or "COPYING" are common names for this file) in your software distribution.
+
+Copyright (c) $YEAR, $ORGANIZATION
+
+Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.
+
+IN NO EVENT SHALL $ORGANISATION BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF $ORGANISATION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+$ORGANISATION SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND $ORGANISATION HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
diff --git a/licenses/org.tros--l2fprod-properties-editor--1.3.x b/licenses/org.tros--l2fprod-properties-editor--1.3.x
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/org.tros--l2fprod-properties-editor--1.3.x
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/licenses/tanukisoft--wrapper--3.2.x b/licenses/tanukisoft--wrapper--3.2.x
new file mode 100644
index 0000000..5529d7b
--- /dev/null
+++ b/licenses/tanukisoft--wrapper--3.2.x
@@ -0,0 +1,1063 @@
+----------------------------------------------------------------------
+----------------- -----------------
+ Tanuki Software, Ltd.
+ Community Software License Agreement
+ Version 1.3
+
+IMPORTANT-READ CAREFULLY: This license agreement is a legal agreement
+between you ("Licensee") and Tanuki Software, Ltd. ("TSI"), which
+includes computer software, associated media, printed materials, and
+may include online or electronic documentation ( Software ). PLEASE
+READ THIS AGREEMENT CAREFULLY BEFORE YOU INSTALL, COPY, DOWNLOAD OR
+USE THE SOFTWARE ACCOMPANYING THIS PACKAGE.
+
+Section 1 - Grant of License
+
+Community editions of the Software are made available on the GNU
+General Public License, Version 2 ("GPLv2") or Version 3 ("GPLv3"),
+included in Sections 4 and 5 of this license document. All sections
+of the Community Software License Agreement must be complied with in
+addition to those of either the GPLv2 or GPLv3. This license allows
+the Software Program to be used with Products that are released under
+either GPLv2 or GPLv3.
+
+
+Section 2 - Definitions
+
+2.1. "Community Edition" shall mean versions of the Software Program
+distributed in source form under this license agreement, and all new
+releases, corrections, enhancements and updates to the Software
+Program, which TSI makes generally available under this agreement.
+
+2.2. "Documentation" shall mean the contents of the website
+describing the functionality and use of the Software Program, located
+at http://wrapper.tanukisoftware.org
+
+2.3. "Product" shall mean the computer programs, that are provided by
+Licensee to Licensee customers or potential customers, and that
+contain both the Software Program as a component of the Product, and a
+component or components (other than the Software Program) that provide
+the material functionality of the Product. If the Product is released
+in source form, the Software Program or any of its components may only
+be included in executable form.
+
+2.4. "Software Program" shall mean the computer software and license
+file provided by TSI under this Agreement, including all new releases,
+corrections, enhancements and updates to such computer software, which
+TSI makes generally available and which Licensee receive pursuant to
+Licensee subscription to TSIMS. Some specific features or platforms
+may not be enabled if they do not fall under the feature set(s)
+covered by the specific license fees paid.
+
+2.5 "End User" shall mean the customers of the Licensee or any
+recipient of the Product whether or not any payment is made to use
+the Product.
+
+
+Section 3 - Licensee Obligations
+
+A copy of this license must be distributed in full with the Product
+in a location that is obvious to any End User.
+
+In accordance with Section 4 (GPLv2) or Section 5 (GPLv3), the full
+source code of all components of the Product must be made available to
+any and all End Users.
+
+Licensee may extend and/or modify the Software Program and distribute
+under the terms of this agreement provided that the copyright notice
+and license information displayed in the console and log files are
+not obfuscated or obstructed in any way.
+
+
+Section 4 - GPLv2 License Agreement
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+ Everyone is permitted to copy and distribute verbatim copies of
+ this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+ freedom to share and change it. By contrast, the GNU General
+ Public License is intended to guarantee your freedom to share and
+ change free software--to make sure the software is free for all
+ its users. This General Public License applies to most of the Free
+ Software Foundation's software and to any other program whose
+ authors commit to using it. (Some other Free Software Foundation
+ software is covered by the GNU Library General Public License
+ instead.) You can apply it to your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+ price. Our General Public Licenses are designed to make sure that
+ you have the freedom to distribute copies of free software (and
+ charge for this service if you wish), that you receive source code
+ or can get it if you want it, that you can change the software or
+ use pieces of it in new free programs; and that you know you can
+ do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+ anyone to deny you these rights or to ask you to surrender the
+ rights. These restrictions translate to certain responsibilities
+ for you if you distribute copies of the software, or if you modify
+ it.
+
+ For example, if you distribute copies of such a program, whether
+ gratis or for a fee, you must give the recipients all the rights
+ that you have. You must make sure that they, too, receive or can
+ get the source code. And you must show them these terms so they
+ know their rights.
+
+ We protect your rights with two steps:
+
+ (1) copyright the software, and
+ (2) offer you this license which gives you legal permission to
+ copy, distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make
+ certain that everyone understands that there is no warranty for
+ this free software. If the software is modified by someone else
+ and passed on, we want its recipients to know that what they have
+ is not the original, so that any problems introduced by others
+ will not reflect on the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+ patents. We wish to avoid the danger that redistributors of a free
+ program will individually obtain patent licenses, in effect making
+ the program proprietary. To prevent this, we have made it clear
+ that any patent must be licensed for everyone's free use or not
+ licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which
+ contains a notice placed by the copyright holder saying it may be
+ distributed under the terms of this General Public License. The
+ "Program", below, refers to any such program or work, and a "work
+ based on the Program" means either the Program or any derivative
+ work under copyright law: that is to say, a work containing the
+ Program or a portion of it, either verbatim or with modifications
+ and/or translated into another language. (Hereinafter, translation
+ is included without limitation in the term "modification".) Each
+ licensee is addressed as "you".
+
+ Activities other than copying, distribution and modification are
+ not covered by this License; they are outside its scope. The act
+ of running the Program is not restricted, and the output from the
+ Program is covered only if its contents constitute a work based on
+ the Program (independent of having been made by running the
+ Program). Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+ source code as you receive it, in any medium, provided that you
+ conspicuously and appropriately publish on each copy an
+ appropriate copyright notice and disclaimer of warranty; keep
+ intact all the notices that refer to this License and to the
+ absence of any warranty; and give any other recipients of the
+ Program a copy of this License along with the Program.
+
+ You may charge a fee for the physical act of transferring a copy,
+ and you may at your option offer warranty protection in exchange
+ for a fee.
+
+ 2. You may modify your copy or copies of the Program or any
+ portion of it, thus forming a work based on the Program, and copy
+ and distribute such modifications or work under the terms of
+ Section 1 above, provided that you also meet all of these
+ conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but does
+ not normally print such an announcement, your work based on the
+ Program is not required to print an announcement.)
+
+ These requirements apply to the modified work as a whole. If
+ identifiable sections of that work are not derived from the
+ Program, and can be reasonably considered independent and separate
+ works in themselves, then this License, and its terms, do not
+ apply to those sections when you distribute them as separate works.
+ But when you distribute the same sections as part of a whole which
+ is a work based on the Program, the distribution of the whole must
+ be on the terms of this License, whose permissions for other
+ licensees extend to the entire whole, and thus to each and every
+ part regardless of who wrote it.
+
+ Thus, it is not the intent of this section to claim rights or
+ contest your rights to work written entirely by you; rather, the
+ intent is to exercise the right to control the distribution of
+ derivative or collective works based on the Program.
+
+ In addition, mere aggregation of another work not based on the
+ Program with the Program (or with a work based on the Program) on
+ a volume of a storage or distribution medium does not bring the
+ other work under the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+ under Section 2) in object code or executable form under the terms
+ of Sections 1 and 2 above provided that you also do one of the
+ following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software
+ interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+ The source code for a work means the preferred form of the work
+ for making modifications to it. For an executable work, complete
+ source code means all the source code for all modules it contains,
+ plus any associated interface definition files, plus the scripts
+ used to control compilation and installation of the executable.
+ However, as a special exception, the source code distributed need
+ not include anything that is normally distributed (in either
+ source or binary form) with the major components (compiler,
+ kernel, and so on) of the operating system on which the executable
+ runs, unless that component itself accompanies the executable.
+
+ If distribution of executable or object code is made by offering
+ access to copy from a designated place, then offering equivalent
+ access to copy the source code from the same place counts as
+ distribution of the source code, even though third parties are not
+ compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+ except as expressly provided under this License. Any attempt
+ otherwise to copy, modify, sublicense or distribute the Program is
+ void, and will automatically terminate your rights under this
+ License. However, parties who have received copies, or rights,
+ from you under this License will not have their licenses
+ terminated so long as such parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+ signed it. However, nothing else grants you permission to modify
+ or distribute the Program or its derivative works. These actions
+ are prohibited by law if you do not accept this License.
+ Therefore, by modifying or distributing the Program (or any work
+ based on the Program), you indicate your acceptance of this
+ License to do so, and all its terms and conditions for copying,
+ distributing or modifying the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on
+ the Program), the recipient automatically receives a license from
+ the original licensor to copy, distribute or modify the Program
+ subject to these terms and conditions. You may not impose any
+ further restrictions on the recipients' exercise of the rights
+ granted herein. You are not responsible for enforcing compliance
+ by third parties to this License.
+
+ 7. If, as a consequence of a court judgment or allegation of
+ patent infringement or for any other reason (not limited to
+ patent issues), conditions are imposed on you (whether by court
+ order, agreement or otherwise) that contradict the conditions of
+ this License, they do not excuse you from the conditions of this
+ License. If you cannot distribute so as to satisfy simultaneously
+ your obligations under this License and any other pertinent
+ obligations, then as a consequence you may not distribute the
+ Program at all. For example, if a patent license would not permit
+ royalty-free redistribution of the Program by all those who
+ receive copies directly or indirectly through you, then the only
+ way you could satisfy both it and this License would be to refrain
+ entirely from distribution of the Program.
+
+ If any portion of this section is held invalid or unenforceable
+ under any particular circumstance, the balance of the section is
+ intended to apply and the section as a whole is intended to apply
+ in other circumstances.
+
+ It is not the purpose of this section to induce you to infringe
+ any patents or other property right claims or to contest validity
+ of any such claims; this section has the sole purpose of
+ protecting the integrity of the free software distribution system,
+ which is implemented by public license practices. Many people have
+ made generous contributions to the wide range of software
+ distributed through that system in reliance on consistent
+ application of that system; it is up to the author/donor to decide
+ if he or she is willing to distribute software through any other
+ system and a licensee cannot impose that choice.
+
+ This section is intended to make thoroughly clear what is believed
+ to be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+ certain countries either by patents or by copyrighted interfaces,
+ the original copyright holder who places the Program under this
+ License may add an explicit geographical distribution limitation
+ excluding those countries, so that distribution is permitted only
+ in or among countries not thus excluded. In such case, this
+ License incorporates the limitation as if written in the body of
+ this License.
+
+ 9. The Free Software Foundation may publish revised and/or new
+ versions of the General Public License from time to time. Such new
+ versions will be similar in spirit to the present version, but may
+ differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+ Program specifies a version number of this License which applies
+ to it and "any later version", you have the option of following
+ the terms and conditions either of that version or of any later
+ version published by the Free Software Foundation. If the Program
+ does not specify a version number of this License, you may choose
+ any version ever published by the Free Software Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other
+ free programs whose distribution conditions are different, write
+ to the author to ask for permission. For software which is
+ copyrighted by the Free Software Foundation, write to the Free
+ Software Foundation; we sometimes make exceptions for this. Our
+ decision will be guided by the two goals of preserving the free
+ status of all derivatives of our free software and of promoting
+ the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
+ WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
+ LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS
+ AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+ PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+ DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR
+ OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+ WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY
+ MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE
+ LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
+ INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
+ INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
+ OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+ OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+
+Section 5 - GPLv3 License Agreement
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright c 2007 Free Software Foundation, Inc.
+
+ Everyone is permitted to copy and distribute verbatim copies of
+ this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+ software and other kinds of works.
+
+ The licenses for most software and other practical works are
+ designed to take away your freedom to share and change the works.
+ By contrast, the GNU General Public License is intended to
+ guarantee your freedom to share and change all versions of a
+ program--to make sure it remains free software for all its users.
+ We, the Free Software Foundation, use the GNU General Public
+ License for most of our software; it applies also to any other
+ work released this way by its authors. You can apply it to your
+ programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+ price. Our General Public Licenses are designed to make sure that
+ you have the freedom to distribute copies of free software (and
+ charge for them if you wish), that you receive source code or can
+ get it if you want it, that you can change the software or use
+ pieces of it in new free programs, and that you know you can do
+ these things.
+
+ To protect your rights, we need to prevent others from denying you
+ these rights or asking you to surrender the rights. Therefore, you
+ have certain responsibilities if you distribute copies of the
+ software, or if you modify it: responsibilities to respect the
+ freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+ gratis or for a fee, you must pass on to the recipients the same
+ freedoms that you received. You must make sure that they, too,
+ receive or can get the source code. And you must show them these
+ terms so they know their rights.
+
+ Developers that use the GNU GPL protect your rights with two
+ steps: (1) assert copyright on the software, and (2) offer you
+ this License giving you legal permission to copy, distribute
+ and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly
+ explains that there is no warranty for this free software. For
+ both users' and authors' sake, the GPL requires that modified
+ versions be marked as changed, so that their problems will not be
+ attributed erroneously to authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+ modified versions of the software inside them, although the
+ manufacturer can do so. This is fundamentally incompatible with
+ the aim of protecting users' freedom to change the software. The
+ systematic pattern of such abuse occurs in the area of products
+ for individuals to use, which is precisely where it is most
+ unacceptable. Therefore, we have designed this version of the GPL
+ to prohibit the practice for those products. If such problems
+ arise substantially in other domains, we stand ready to extend
+ this provision to those domains in future versions of the GPL, as
+ needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software
+ patents. States should not allow patents to restrict development
+ and use of software on general-purpose computers, but in those
+ that do, we wish to avoid the special danger that patents applied
+ to a free program could make it effectively proprietary. To
+ prevent this, the GPL assures that patents cannot be used to
+ render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public
+ License.
+
+ "Copyright" also means copyright-like laws that apply to other
+ kinds of works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+ License. Each licensee is addressed as "you". "Licensees" and
+ "recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the
+ work in a fashion requiring copyright permission, other than the
+ making of an exact copy. The resulting work is called a "modified
+ version" of the earlier work or a work "based on" the earlier
+ work.
+
+ A "covered work" means either the unmodified Program or a work
+ based on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+ permission, would make you directly or secondarily liable for
+ infringement under applicable copyright law, except executing it
+ on a computer or modifying a private copy. Propagation includes
+ copying, distribution (with or without modification), making
+ available to the public, and in some countries other activities as
+ well.
+
+ To "convey" a work means any kind of propagation that enables
+ other parties to make or receive copies. Mere interaction with a
+ user through a computer network, with no transfer of a copy, is
+ not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+ to the extent that it includes a convenient and prominently
+ visible feature that (1) displays an appropriate copyright notice,
+ and (2) tells the user that there is no warranty for the work
+ (except to the extent that warranties are provided), that
+ licensees may convey the work under this License, and how to view
+ a copy of this License. If the interface presents a list of user
+ commands or options, such as a menu, a prominent item in the list
+ meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+ for making modifications to it. "Object code" means any non-source
+ form of a work.
+
+ A "Standard Interface" means an interface that either is an
+ official standard defined by a recognized standards body, or, in
+ the case of interfaces specified for a particular programming
+ language, one that is widely used among developers working in that
+ language.
+
+ The "System Libraries" of an executable work include anything,
+ other than the work as a whole, that (a) is included in the normal
+ form of packaging a Major Component, but which is not part of that
+ Major Component, and (b) serves only to enable use of the work
+ with that Major Component, or to implement a Standard Interface
+ for which an implementation is available to the public in source
+ code form. A "Major Component", in this context, means a major
+ essential component (kernel, window system, and so on) of the
+ specific operating system (if any) on which the executable work
+ runs, or a compiler used to produce the work, or an object code
+ interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means
+ all the source code needed to generate, install, and (for an
+ executable work) run the object code and to modify the work,
+ including scripts to control those activities. However, it does
+ not include the work's System Libraries, or general-purpose tools
+ or generally available free programs which are used unmodified in
+ performing those activities but which are not part of the work.
+ For example, Corresponding Source includes interface definition
+ files associated with source files for the work, and the source
+ code for shared libraries and dynamically linked subprograms that
+ the work is specifically designed to require, such as by intimate
+ data communication or control flow between those subprograms and
+ other parts of the work.
+
+ The Corresponding Source need not include anything that users can
+ regenerate automatically from other parts of the Corresponding
+ Source.
+
+ The Corresponding Source for a work in source code form is that
+ same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+ copyright on the Program, and are irrevocable provided the stated
+ conditions are met. This License explicitly affirms your unlimited
+ permission to run the unmodified Program. The output from running
+ a covered work is covered by this License only if the output,
+ given its content, constitutes a covered work. This License
+ acknowledges your rights of fair use or other equivalent, as
+ provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+ convey, without conditions so long as your license otherwise
+ remains in force. You may convey covered works to others for the
+ sole purpose of having them make modifications exclusively for
+ you, or provide you with facilities for running those works,
+ provided that you comply with the terms of this License in
+ conveying all material for which you do not control copyright.
+ Those thus making or running the covered works for you must do
+ so exclusively on your behalf, under your direction and control,
+ on terms that prohibit them from making any copies of your
+ copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+ the conditions stated below. Sublicensing is not allowed; section
+ 10 makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+ measure under any applicable law fulfilling obligations under
+ article 11 of the WIPO copyright treaty adopted on 20 December
+ 1996, or similar laws prohibiting or restricting circumvention of
+ such measures.
+
+ When you convey a covered work, you waive any legal power to
+ forbid circumvention of technological measures to the extent such
+ circumvention is effected by exercising rights under this License
+ with respect to the covered work, and you disclaim any intention
+ to limit operation or modification of the work as a means of
+ enforcing, against the work's users, your or third parties' legal
+ rights to forbid circumvention of technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+ receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright
+ notice; keep intact all notices stating that this License and any
+ non-permissive terms added in accord with section 7 apply to the
+ code; keep intact all notices of the absence of any warranty;
+ and give all recipients a copy of this License along with the
+ Program.
+
+ You may charge any price or no price for each copy that you
+ convey, and you may offer support or warranty protection for a
+ fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications
+ to produce it from the Program, in the form of source code under
+ the terms of section 4, provided that you also meet all of these
+ conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to "keep
+ intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and
+ independent works, which are not by their nature extensions of the
+ covered work, and which are not combined with it such as to form a
+ larger program, in or on a volume of a storage or distribution
+ medium, is called an "aggregate" if the compilation and its
+ resulting copyright are not used to limit the access or legal
+ rights of the compilation's users beyond what the individual works
+ permit. Inclusion of a covered work in an aggregate does not cause
+ this License to apply to the other parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+ of sections 4 and 5, provided that you also convey the machine-
+ readable Corresponding Source under the terms of this License, in
+ one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the Corresponding
+ Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission,
+ provided you inform other peers where the object code and
+ Corresponding Source of the work are being offered to the general
+ public at no charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is
+ excluded from the Corresponding Source as a System Library, need
+ not be included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means
+ any tangible personal property which is normally used for
+ personal, family, or household purposes, or (2) anything designed
+ or sold for incorporation into a dwelling. In determining whether
+ a product is a consumer product, doubtful cases shall be resolved
+ in favor of coverage. For a particular product received by a
+ particular user, "normally used" refers to a typical or common use
+ of that class of product, regardless of the status of the
+ particular user or of the way in which the particular user
+ actually uses, or expects or is expected to use, the product. A
+ product is a consumer product regardless of whether the product
+ has substantial commercial, industrial or non-consumer uses,
+ unless such uses represent the only significant mode of use of the
+ product.
+
+ "Installation Information" for a User Product means any methods,
+ procedures, authorization keys, or other information required to
+ install and execute modified versions of a covered work in that
+ User Product from a modified version of its Corresponding Source.
+ The information must suffice to ensure that the continued
+ functioning of the modified object code is in no case prevented or
+ interfered with solely because modification has been made.
+
+ If you convey an object code work under this section in, or with,
+ or specifically for use in, a User Product, and the conveying
+ occurs as part of a transaction in which the right of possession
+ and use of the User Product is transferred to the recipient in
+ perpetuity or for a fixed term (regardless of how the transaction
+ is characterized), the Corresponding Source conveyed under this
+ section must be accompanied by the Installation Information. But
+ this requirement does not apply if neither you nor any third party
+ retains the ability to install modified object code on the User
+ Product (for example, the work has been installed in ROM).
+
+ The requirement to provide Installation Information does not
+ include a requirement to continue to provide support service,
+ warranty, or updates for a work that has been modified or
+ installed by the recipient, or for the User Product in which it
+ has been modified or installed. Access to a network may be denied
+ when the modification itself materially and adversely affects the
+ operation of the network or violates the rules and protocols for
+ communication across the network.
+
+ Corresponding Source conveyed, and Installation Information
+ provided, in accord with this section must be in a format that is
+ publicly documented (and with an implementation available to the
+ public in source code form), and must require no special password
+ or key for unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of
+ this License by making exceptions from one or more of its
+ conditions. Additional permissions that are applicable to the
+ entire Program shall be treated as though they were included in
+ this License, to the extent that they are valid under applicable
+ law. If additional permissions apply only to part of the Program,
+ that part may be used separately under those permissions, but the
+ entire Program remains governed by this License without regard to
+ the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+ remove any additional permissions from that copy, or from any part
+ of it. (Additional permissions may be written to require their own
+ removal in certain cases when you modify the work.) You may place
+ additional permissions on material, added by you to a covered
+ work, for which you have or can give appropriate copyright
+ permission.
+
+ Notwithstanding any other provision of this License, for material
+ you add to a covered work, you may (if authorized by the copyright
+ holders of that material) supplement the terms of this License
+ with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material,
+ or requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors
+ or authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions
+ of it) with contractual assumptions of liability to the recipient,
+ for any liability that these contractual assumptions directly
+ impose on those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+ restrictions" within the meaning of section 10. If the Program as
+ you received it, or any part of it, contains a notice stating that
+ it is governed by this License along with a term that is a further
+ restriction, you may remove that term. If a license document
+ contains a further restriction but permits relicensing or
+ conveying under this License, you may add to a covered work
+ material governed by the terms of that license document, provided
+ that the further restriction does not survive such relicensing or
+ conveying.
+
+ If you add terms to a covered work in accord with this section,
+ you must place, in the relevant source files, a statement of the
+ additional terms that apply to those files, or a notice indicating
+ where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in
+ the form of a separately written license, or stated as exceptions;
+ the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+ provided under this License. Any attempt otherwise to propagate or
+ modify it is void, and will automatically terminate your rights
+ under this License (including any patent licenses granted under
+ the third paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+ license from a particular copyright holder is reinstated (a)
+ provisionally, unless and until the copyright holder explicitly
+ and finally terminates your license, and (b) permanently, if the
+ copyright holder fails to notify you of the violation by some
+ reasonable means prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+ reinstated permanently if the copyright holder notifies you of the
+ violation by some reasonable means, this is the first time you
+ have received notice of violation of this License (for any work)
+ from that copyright holder, and you cure the violation prior to 30
+ days after your receipt of the notice.
+
+ Termination of your rights under this section does not terminate
+ the licenses of parties who have received copies or rights from
+ you under this License. If your rights have been terminated and
+ not permanently reinstated, you do not qualify to receive new
+ licenses for the same material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+ run a copy of the Program. Ancillary propagation of a covered work
+ occurring solely as a consequence of using peer-to-peer
+ transmission to receive a copy likewise does not require
+ acceptance. However, nothing other than this License grants you
+ permission to propagate or modify any covered work. These actions
+ infringe copyright if you do not accept this License. Therefore,
+ by modifying or propagating a covered work, you indicate your
+ acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+ receives a license from the original licensors, to run, modify and
+ propagate that work, subject to this License. You are not
+ responsible for enforcing compliance by third parties with this
+ License.
+
+ An "entity transaction" is a transaction transferring control of
+ an organization, or substantially all assets of one, or
+ subdividing an organization, or merging organizations. If
+ propagation of a covered work results from an entity transaction,
+ each party to that transaction who receives a copy of the work
+ also receives whatever licenses to the work the party's
+ predecessor in interest had or could give under the previous
+ paragraph, plus a right to possession of the Corresponding Source
+ of the work from the predecessor in interest, if the predecessor
+ has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+ rights granted or affirmed under this License. For example, you
+ may not impose a license fee, royalty, or other charge for
+ exercise of rights granted under this License, and you may not
+ initiate litigation (including a cross-claim or counterclaim in a
+ lawsuit) alleging that any patent claim is infringed by making,
+ using, selling, offering for sale, or importing the Program or any
+ portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under
+ this License of the Program or a work on which the Program is
+ based. The work thus licensed is called the contributor's
+ "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+ owned or controlled by the contributor, whether already acquired
+ or hereafter acquired, that would be infringed by some manner,
+ permitted by this License, of making, using, or selling its
+ contributor version, but do not include claims that would be
+ infringed only as a consequence of further modification of the
+ contributor version. For purposes of this definition, "control"
+ includes the right to grant patent sublicenses in a manner
+ consistent with the requirements of this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-
+ free patent license under the contributor's essential patent
+ claims, to make, use, sell, offer for sale, import and otherwise
+ run, modify and propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any
+ express agreement or commitment, however denominated, not to
+ enforce a patent (such as an express permission to practice a
+ patent or covenant not to sue for patent infringement). To "grant"
+ such a patent license to a party means to make such an agreement
+ or commitment not to enforce a patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent
+ license, and the Corresponding Source of the work is not available
+ for anyone to copy, free of charge and under the terms of this
+ License, through a publicly available network server or other
+ readily accessible means, then you must either (1) cause the
+ Corresponding Source to be so available, or (2) arrange to deprive
+ yourself of the benefit of the patent license for this particular
+ work, or (3) arrange, in a manner consistent with the requirements
+ of this License, to extend the patent license to downstream
+ recipients. "Knowingly relying" means you have actual knowledge
+ that, but for the patent license, your conveying the covered work
+ in a country, or your recipient's use of the covered work in a
+ country, would infringe one or more identifiable patents in that
+ country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+ arrangement, you convey, or propagate by procuring conveyance of,
+ a covered work, and grant a patent license to some of the parties
+ receiving the covered work authorizing them to use, propagate,
+ modify or convey a specific copy of the covered work, then the
+ patent license you grant is automatically extended to all
+ recipients of the covered work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+ the scope of its coverage, prohibits the exercise of, or is
+ conditioned on the non-exercise of one or more of the rights that
+ are specifically granted under this License. You may not convey a
+ covered work if you are a party to an arrangement with a third
+ party that is in the business of distributing software, under
+ which you make payment to the third party based on the extent of
+ your activity of conveying the work, and under which the third
+ party grants, to any of the parties who would receive the covered
+ work from you, a discriminatory patent license (a) in connection
+ with copies of the covered work conveyed by you (or copies made
+ from those copies), or (b) primarily for and in connection with
+ specific products or compilations that contain the covered work,
+ unless you entered into that arrangement, or that patent license
+ was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or
+ limiting any implied license or other defenses to infringement
+ that may otherwise be available to you under applicable patent
+ law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order,
+ agreement or otherwise) that contradict the conditions of this
+ License, they do not excuse you from the conditions of this
+ License. If you cannot convey a covered work so as to satisfy
+ simultaneously your obligations under this License and any other
+ pertinent obligations, then as a consequence you may not convey it
+ at all. For example, if you agree to terms that obligate you to
+ collect a royalty for further conveying from those to whom you
+ convey the Program, the only way you could satisfy both those
+ terms and this License would be to refrain entirely from conveying
+ the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+ permission to link or combine any covered work with a work
+ licensed under version 3 of the GNU Affero General Public License
+ into a single combined work, and to convey the resulting work. The
+ terms of this License will continue to apply to the part which is
+ the covered work, but the special requirements of the GNU Affero
+ General Public License, section 13, concerning interaction through
+ a network will apply to the combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new
+ versions of the GNU General Public License from time to time. Such
+ new versions will be similar in spirit to the present version, but
+ may differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+ Program specifies that a certain numbered version of the GNU
+ General Public License "or any later version" applies to it, you
+ have the option of following the terms and conditions either of
+ that numbered version or of any later version published by the
+ Free Software Foundation. If the Program does not specify a
+ version number of the GNU General Public License, you may choose
+ any version ever published by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+ versions of the GNU General Public License can be used, that
+ proxy's public statement of acceptance of a version permanently
+ authorizes you to choose that version for the Program.
+
+ Later license versions may give you additional or different
+ permissions. However, no additional obligations are imposed on any
+ author or copyright holder as a result of your choosing to follow
+ a later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+ COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE
+ RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.
+ SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+ NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+ WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES
+ AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
+ FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
+ THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
+ BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+ above cannot be given local legal effect according to their terms,
+ reviewing courts shall apply local law that most closely
+ approximates an absolute waiver of all civil liability in
+ connection with the Program, unless a warranty or assumption of
+ liability accompanies a copy of the Program in return for a fee.
+
+
+Section 6 - 3rd Party Components
+
+(1) The Software Program includes software and documentation components
+developed in part by Silver Egg Technology, Inc.("SET") prior to 2001
+and released under the following license.
+
+ Copyright (c) 2001 Silver Egg Technology
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sub-license, and/or
+ sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/sample-pipeline/build-env.sh b/sample-pipeline/build-env.sh
new file mode 100755
index 0000000..05dbf06
--- /dev/null
+++ b/sample-pipeline/build-env.sh
@@ -0,0 +1,57 @@
+#! /bin/bash
+#
+# Shell script that builds the Python environment for the sample pipeline.
+#
+# The environment variable PIPELINE_CONFIG_PATH contains the path to
+# the pipeline configuration file. This script uses that path to
+# derive all the paths it needs.
+#
+# Author: PT
+# Author: Bill Wohler
+
+etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+sample_root="$(dirname "$etc_dir")"
+sample_home="$sample_root/build"
+python_env=$sample_home/env
+ziggy_root="$(dirname "$sample_root")"
+
+# Put the build directory next to the env directory in the directory tree.
+mkdir -p $python_env
+
+# Create and populate the data receipt directory from the sample data
+data_receipt_dir=$sample_home/pipeline-results/data-receipt
+mkdir -p $data_receipt_dir
+cp $sample_root/data/* $data_receipt_dir
+
+# Build the bin directory in build.
+bin_dir=$sample_home/bin
+mkdir -p $bin_dir
+bin_src_dir=$sample_root/src/main/sh
+
+# Copy the shell scripts from src to build.
+install -m a+rx $bin_src_dir/permuter.sh $bin_dir/permuter
+install -m a+rx $bin_src_dir/flip.sh $bin_dir/flip
+install -m a+rx $bin_src_dir/averaging.sh $bin_dir/averaging
+
+python3 -m venv $python_env
+
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $python_env/bin/activate
+
+# Build the environment with the needed packages.
+pip3 install h5py Pillow numpy
+
+# Get the location of the environment's site packages directory
+site_pkgs=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Copy the pipeline major_tom package to the site-packages location.
+cp -r $ziggy_root/sample-pipeline/src/main/python/major_tom $site_pkgs
+
+# Copy the Ziggy components to the site-packages location.
+cp -r $ziggy_root/src/main/python/hdf5mi $site_pkgs
+cp -r $ziggy_root/src/main/python/zigutils $site_pkgs
+
+exit 0
diff --git a/sample-pipeline/config/pd-sample.xml b/sample-pipeline/config/pd-sample.xml
new file mode 100644
index 0000000..65c55f1
--- /dev/null
+++ b/sample-pipeline/config/pd-sample.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/config/pe-sample.xml b/sample-pipeline/config/pe-sample.xml
new file mode 100644
index 0000000..5f473a3
--- /dev/null
+++ b/sample-pipeline/config/pe-sample.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/sample-pipeline/config/pl-sample.xml b/sample-pipeline/config/pl-sample.xml
new file mode 100644
index 0000000..a6009ff
--- /dev/null
+++ b/sample-pipeline/config/pl-sample.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/config/pt-sample.xml b/sample-pipeline/config/pt-sample.xml
new file mode 100644
index 0000000..a655533
--- /dev/null
+++ b/sample-pipeline/config/pt-sample.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/data/nasa_logo-set-1-file-0.png b/sample-pipeline/data/nasa_logo-set-1-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-1-file-0.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-1-file-1.png b/sample-pipeline/data/nasa_logo-set-1-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-1-file-1.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-1-file-2.png b/sample-pipeline/data/nasa_logo-set-1-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-1-file-2.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-1-file-3.png b/sample-pipeline/data/nasa_logo-set-1-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-1-file-3.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-2-file-0.png b/sample-pipeline/data/nasa_logo-set-2-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-2-file-0.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-2-file-1.png b/sample-pipeline/data/nasa_logo-set-2-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-2-file-1.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-2-file-2.png b/sample-pipeline/data/nasa_logo-set-2-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-2-file-2.png differ
diff --git a/sample-pipeline/data/nasa_logo-set-2-file-3.png b/sample-pipeline/data/nasa_logo-set-2-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/data/nasa_logo-set-2-file-3.png differ
diff --git a/sample-pipeline/data/sample-model.txt b/sample-pipeline/data/sample-model.txt
new file mode 100644
index 0000000..2c36741
--- /dev/null
+++ b/sample-pipeline/data/sample-model.txt
@@ -0,0 +1,44 @@
+This is a sample model for the sample Ziggy pipeline.
+
+It's a plain text file.
+
+A model can be anything: an ASCII file, a binary file, even a directory! The main thing
+about it is that, unlike data files, all versions of every model file are kept forever,
+and never get overwritten.
+
+So how does that work? More to the point, how is it that this "model" can get read in
+over and over again and never get overwritten, even though it's got the same file name
+each time?
+
+The way it works is that every time a given model gets read into the datastore, the
+resulting model file gets a version number and an import date appended to the file name.
+When the model is staged out to the task directories, the version number and import date
+are stripped off. In this way, we can manage different versions of a model that all have
+the same name and never have a problem!
+
+In some cases, though, the project may decide that a model is going to have a version
+number and/or version date in the filename. So for example, this file could have been
+called "sample-model-version-1-20220830.txt", and there's a way to define the model
+name convention such that Ziggy knows that the version number and the datestamp
+occur at specific points in the file name: specifically, define the model data type in
+pt-sample.xml as follows:
+
+
+
+This tells Ziggy that the first regex group should be used as the version number and the
+second should be used as the timestamp.
+
+A big advantage of including the version number and version date in the filename is that
+if you do that, Ziggy can tell if you're reimporting a model that's already in the datastore,
+and prevent you from doing that. It also prevents you from taking a new model, giving it
+the model ID of the old model, and then importing that (which would cause the old model
+to get deleted, and we don't want that). Without that information, Ziggy will allow you
+to import the same model as many times as you like, and Ziggy will dutifully put a new
+version number and date on it and keep it forever.
+
+The reason I didn't put a version number and version date into this model's name is that
+I wanted you, the user, to be able to run data receipt over and over again without any
+errors! If the model has a version number, then the 2nd time you try to import it, you'll
+get an error.
diff --git a/sample-pipeline/data/sample-pipeline-manifest.xml b/sample-pipeline/data/sample-pipeline-manifest.xml
new file mode 100644
index 0000000..97e2456
--- /dev/null
+++ b/sample-pipeline/data/sample-pipeline-manifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/etc/sample.properties b/sample-pipeline/etc/sample.properties
new file mode 100644
index 0000000..11bce3f
--- /dev/null
+++ b/sample-pipeline/etc/sample.properties
@@ -0,0 +1,29 @@
+data.receipt.dir = ${pipeline.results.dir}/data-receipt
+database.connections = 100
+database.dir = ${pipeline.results.dir}/pg
+database.host = localhost
+database.name = majortom
+database.port = 5438
+database.software.name = postgresql
+datastore.root.dir = ${pipeline.results.dir}/datastore
+hibernate.connection.username=${env:USER}
+moduleExe.memdrone.enabled = false
+moduleExe.memdrone.sleepSeconds = 10
+moduleExe.useSymlinks = true
+pi.moduleExe.environment = ZIGGY_HOME=${env:ZIGGY_HOME}
+pi.worker.heapSize = 12000
+pi.worker.moduleExe.binPath = ${env:JAVA_HOME}/bin:/bin:/usr/bin
+pi.worker.threadCount = 2
+pipeline.definition.dir = ${pipeline.root.dir}/config
+pipeline.home.dir = ${pipeline.root.dir}/build
+pipeline.logoFile = ${ziggy.home.dir}/resources/main/images/nasa-logo.png
+pipeline.results.dir = ${pipeline.home.dir}/pipeline-results
+pipeline.root.dir = ${ziggy.root}/sample-pipeline
+remote.cluster.name = nas
+remote.group = phony
+remote.host = phony
+remote.user = phony
+rmi.registry.port = 1099
+ziggy.config.path = ${ziggy.home.dir}/etc/ziggy.properties
+ziggy.home.dir = ${ziggy.root}/build
+ziggy.root = ${env:ZIGGY_ROOT}
\ No newline at end of file
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-0.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-1.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-2.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-3.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-3-file-3.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-0.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-1.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-2.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-3.png b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-1/nasa_logo-set-4-file-3.png differ
diff --git a/sample-pipeline/multi-data/sample-1/sample-1-pipeline-manifest.xml b/sample-pipeline/multi-data/sample-1/sample-1-pipeline-manifest.xml
new file mode 100644
index 0000000..2fe4763
--- /dev/null
+++ b/sample-pipeline/multi-data/sample-1/sample-1-pipeline-manifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-0.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-1.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-2.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-3.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-5-file-3.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-0.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-1.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-2.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-3.png b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-2/nasa_logo-set-6-file-3.png differ
diff --git a/sample-pipeline/multi-data/sample-2/sample-2-pipeline-manifest.xml b/sample-pipeline/multi-data/sample-2/sample-2-pipeline-manifest.xml
new file mode 100644
index 0000000..0991f4f
--- /dev/null
+++ b/sample-pipeline/multi-data/sample-2/sample-2-pipeline-manifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-0.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-1.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-2.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-3.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-3.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-7-file-3.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-0.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-0.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-0.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-1.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-1.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-1.png differ
diff --git a/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-2.png b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-2.png
new file mode 100644
index 0000000..52ff8f3
Binary files /dev/null and b/sample-pipeline/multi-data/sample-3/nasa_logo-set-8-file-2.png differ
diff --git a/sample-pipeline/multi-data/sample-3/sample-3-pipeline-manifest.xml b/sample-pipeline/multi-data/sample-3/sample-3-pipeline-manifest.xml
new file mode 100644
index 0000000..093578e
--- /dev/null
+++ b/sample-pipeline/multi-data/sample-3/sample-3-pipeline-manifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-pipeline/src/main/python/major_tom/__init__.py b/sample-pipeline/src/main/python/major_tom/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/sample-pipeline/src/main/python/major_tom/averaging.py b/sample-pipeline/src/main/python/major_tom/averaging.py
new file mode 100644
index 0000000..ec4959b
--- /dev/null
+++ b/sample-pipeline/src/main/python/major_tom/averaging.py
@@ -0,0 +1,44 @@
+'''
+Wraps the image-averaging Python function in the Ziggy elements needed for things like
+input/output management and exception handling.
+
+@author: PT
+'''
+from zigutils.stacktrace import ZiggyErrorWriter
+from hdf5mi.hdf5 import Hdf5ModuleInterface
+from zigutils.pidfile import write_pid_file
+from sys import exit
+
+from major_tom import average_images
+import os
+import time
+
+# Define the HDF5 read/write class as a global variable.
+hdf5_module_interface = Hdf5ModuleInterface()
+
+if __name__ == '__main__':
+ try:
+
+ # Generate the PID file.
+ write_pid_file()
+
+ # Read inputs: note that the inputs contain the names of all the files
+ # that are to be used in this process, as well as model names and
+ # parameters. All files are in the working directory.
+ inputs = hdf5_module_interface.read_file("averaging-inputs-0.h5")
+ data_files = inputs.dataFilenames
+ parameters = inputs.moduleParameters.Algorithm_Parameters
+ models = inputs.modelFilenames
+
+ # Run the averaging function.
+ average_images(data_files)
+
+ # Sleep for a user-specified interval. This is here just so the
+ # user can watch execution run on the pipeline console.
+ time.sleep(parameters.execution_pause_seconds)
+ print("Flip pipeline module completed")
+ exit(0)
+
+ except Exception:
+ ZiggyErrorWriter()
+ exit(1)
diff --git a/sample-pipeline/src/main/python/major_tom/flip.py b/sample-pipeline/src/main/python/major_tom/flip.py
new file mode 100644
index 0000000..d2d26bd
--- /dev/null
+++ b/sample-pipeline/src/main/python/major_tom/flip.py
@@ -0,0 +1,47 @@
+'''
+Wraps the left_right_flip and up_down_flip Python function in the Ziggy elements needed for things like
+input/output management and exception handling.
+
+@author: PT
+'''
+from zigutils.stacktrace import ZiggyErrorWriter
+from hdf5mi.hdf5 import Hdf5ModuleInterface
+from zigutils.pidfile import write_pid_file
+from sys import exit
+
+from major_tom import left_right_flip, up_down_flip
+import os
+import time
+
+# Define the HDF5 read/write class as a global variable.
+hdf5_module_interface = Hdf5ModuleInterface()
+
+if __name__ == '__main__':
+ try:
+
+ # Generate the PID file.
+ write_pid_file()
+
+ # Read inputs: note that the inputs contain the names of all the files
+ # that are to be used in this process, as well as model names and
+ # parameters. All files are in the working directory.
+ inputs = hdf5_module_interface.read_file("flip-inputs-0.h5")
+ data_file = inputs.dataFilenames
+ parameters = inputs.moduleParameters.Algorithm_Parameters
+ models = inputs.modelFilenames
+
+ # Run the two flippers and allow them to save their outputs
+ print("Start with left-right flip")
+ left_right_flip(data_file)
+ print("Moving on to up-down flip")
+ up_down_flip(data_file)
+
+ # Sleep for a user-specified interval. This is here just so the
+ # user can watch execution run on the pipeline console.
+ time.sleep(parameters.execution_pause_seconds)
+ print("Flip pipeline module completed")
+ exit(0)
+
+ except Exception:
+ ZiggyErrorWriter()
+ exit(1)
diff --git a/sample-pipeline/src/main/python/major_tom/major_tom.py b/sample-pipeline/src/main/python/major_tom/major_tom.py
new file mode 100644
index 0000000..7c61220
--- /dev/null
+++ b/sample-pipeline/src/main/python/major_tom/major_tom.py
@@ -0,0 +1,143 @@
+'''
+Python source code for the sample pipeline that shows off Ziggy's capabilities in
+an extremely simple use-case. Each processing step in the pipeline does something simple to
+a Portable Network Graphics (PNG) formatted image file.
+
+This is a kind of ludicrous pipeline that does stupid things to trivial amounts of data.
+Also, I'm terrible at Python programming, so this might be written in a totally awful
+Python idiom. However: the idea is that the subject matter experts would write the actual
+analysis code however they feel like writing it, test it, etc., outside of the context
+of the pipeline. Once that's done the process of connecting the results to Ziggy and running
+them in the pipeline is straightforward. That's the theory, at least.
+
+@author: PT
+'''
+
+from PIL import Image
+import numpy as np
+import os
+import re
+
+# Takes a PNG image and permutes the R, G, B components.
+def permute_color(filename, throw_exception, generate_output):
+
+ # If generate_output and throw_exception are both false, we can return
+ # 0 here and be done with it.
+ if not throw_exception and not generate_output:
+ print("generate_output false, retcode zero but no output generated")
+ return
+
+ # If throw_exception is true, throw an exception.
+ if throw_exception:
+ raise ValueError("Value error raised because throw_exception is true")
+
+ # Read in the PNG file and extract the colors.
+ png_file = Image.open(filename)
+
+ png_array = np.array(png_file)
+ red_image = np.copy(png_array[:,:,0])
+ green_image = np.copy(png_array[:,:,1])
+ blue_image = np.copy(png_array[:,:,2])
+
+ # Permute the colors, but keep the transparency the same as in the original
+ # image.
+
+ indices = np.random.permutation([0, 1, 2])
+ png_array[:,:,indices[0]] = red_image
+ png_array[:,:,indices[1]] = green_image
+ png_array[:,:,indices[2]] = blue_image
+
+ bare_filename = os.path.splitext(filename)[0];
+ save_filename = bare_filename + "-perm.png"
+ print("Saving color-permuted image to file {} in directory {}".format(save_filename, os.getcwd()))
+
+ new_png_file = Image.fromarray(png_array, 'RGBA')
+ new_png_file.save(save_filename)
+
+ return
+
+# Takes a PNG image and performs a left-right flip.
+def left_right_flip(filename):
+
+ # Read in the PNG file and extract the colors and the transparency.
+ png_file = Image.open(filename)
+
+ png_array = np.array(png_file)
+ red_image = png_array[:,:,0]
+ green_image = png_array[:,:,1]
+ blue_image = png_array[:,:,2]
+ alpha_image = png_array[:,:,3]
+
+ png_array[:,:,0] = np.fliplr(red_image)
+ png_array[:,:,1] = np.fliplr(green_image)
+ png_array[:,:,2] = np.fliplr(blue_image)
+ png_array[:,:,3] = np.fliplr(alpha_image)
+
+ bare_filename = os.path.splitext(filename)[0];
+ save_filename = bare_filename + "-lrflip.png"
+ print("Saving LR-flipped image to file {} in directory {}".format(save_filename, os.getcwd()))
+
+ new_png_file = Image.fromarray(png_array)
+ new_png_file.save(save_filename)
+
+ return
+
+# Takes a PNG image and performs an up-down flip.
+def up_down_flip(filename):
+
+ # Read in the PNG file and extract the colors and the transparency.
+ png_file = Image.open(filename)
+
+ png_array = np.array(png_file)
+ red_image = png_array[:,:,0]
+ green_image = png_array[:,:,1]
+ blue_image = png_array[:,:,2]
+ alpha_image = png_array[:,:,3]
+
+ png_array[:,:,0] = np.flipud(red_image)
+ png_array[:,:,1] = np.flipud(green_image)
+ png_array[:,:,2] = np.flipud(blue_image)
+ png_array[:,:,3] = np.flipud(alpha_image)
+
+ bare_filename = os.path.splitext(filename)[0];
+ save_filename = bare_filename + "-udflip.png"
+ print("Saving UD-flipped image to file {} in directory {}".format(save_filename, os.getcwd()))
+
+ new_png_file = Image.fromarray(png_array)
+ new_png_file.save(save_filename)
+
+ return
+
+# Takes the mean of a series of PNG images.
+def average_images(filenames):
+
+ n_images = len(filenames)
+ i_image = 0
+
+ # Extract the dataset string.
+ pattern="(\\S+?)-(set-[0-9])-(file-[0-9])-perm-(\\S+?).png"
+ match = re.search(pattern, filenames[0])
+ setString = match.group(2);
+ for filename in filenames:
+
+ # Read in the PNG file.
+ png_file = Image.open(filename)
+ png_array = np.array(png_file)
+
+ # Accumulate the image into sum_array.
+ if i_image == 0:
+ sum_array = np.copy(png_array)
+ else:
+ sum_array += png_array
+ i_image += 1
+
+ mean_array = sum_array // n_images
+ save_filename = 'averaged-image-' + setString + '.png'
+
+ print("Saving averaged image to file {} in directory {}".format(save_filename, os.getcwd()))
+
+ new_png_file = Image.fromarray(mean_array)
+ new_png_file.save(save_filename)
+
+ return
+
diff --git a/sample-pipeline/src/main/python/major_tom/permuter.py b/sample-pipeline/src/main/python/major_tom/permuter.py
new file mode 100644
index 0000000..d520693
--- /dev/null
+++ b/sample-pipeline/src/main/python/major_tom/permuter.py
@@ -0,0 +1,58 @@
+'''
+Wraps the permute_color Python function in the Ziggy elements needed for things like input/output
+management and exception handling.
+
+@author: PT
+'''
+
+from zigutils.stacktrace import ZiggyErrorWriter
+from hdf5mi.hdf5 import Hdf5ModuleInterface
+from zigutils.pidfile import write_pid_file
+from sys import exit
+from major_tom import permute_color
+import os
+import time
+
+# Define the HDF5 read/write class as a global variable.
+hdf5_module_interface = Hdf5ModuleInterface()
+
+if __name__ == '__main__':
+ try:
+
+ # Generate the PID file.
+ write_pid_file()
+
+ # Read inputs: note that the inputs contain the names of all the files
+ # that are to be used in this process, as well as model names and
+ # parameters. All files are in the working directory.
+ inputs = hdf5_module_interface.read_file("permuter-inputs-0.h5")
+ data_file = inputs.dataFilenames
+ parameters = inputs.moduleParameters.Algorithm_Parameters
+ models = inputs.modelFilenames
+
+ # Handle the parameter values that can cause an error or cause
+ # execution to complete without generating output.
+ dir_name = os.path.basename(os.getcwd())
+ if dir_name == "st-0":
+ throw_exception = parameters.throw_exception_subtask_0
+ else:
+ throw_exception = False
+
+ if dir_name == "st-1":
+ produce_output = parameters.produce_output_subtask_1
+ else:
+ produce_output = True
+
+ # Run the color permuter. The permute_color function will produce
+ # the output with the correct filename.
+ permute_color(data_file, throw_exception, produce_output)
+
+ # Sleep for a user-specified interval. This is here just so the
+ # user can watch execution run on the pipeline console.
+ time.sleep(parameters.execution_pause_seconds)
+ exit(0)
+
+ except Exception:
+ ZiggyErrorWriter()
+ exit(1)
+
diff --git a/sample-pipeline/src/main/sh/averaging.sh b/sample-pipeline/src/main/sh/averaging.sh
new file mode 100755
index 0000000..8f58b59
--- /dev/null
+++ b/sample-pipeline/src/main/sh/averaging.sh
@@ -0,0 +1,48 @@
+#! /bin/bash
+#
+# Shell script that runs the image-averaging pipeline module.
+#
+# The default assumption is that the PIPELINE_CONFIG_PATH environment variable points
+# at the etc/sample.properties file in the sample-pipeline directory. In this case,
+# the known location of that file allows the script to find everything else it needs
+# relative to that location.
+#
+# Alternately, the user may specify two environment variables:
+# SAMPLE_PIPELINE_PYTHON_ENV specifies the location of the Python
+# environment constructed by build-env.sh.
+# ZIGGY_HOME specifies the top-level Ziggy directory.
+#
+# Note that if you point PIPELINE_CONFIG_PATH at a different file (if, for example,
+# you copy the file that ships with Ziggy to a different location so you can edit it
+# without running afoul of Git change tracking), you will need to define ZIGGY_HOME
+# and SAMPLE_PIPELINE_PYTHON_ENV variables!
+#
+# Author: PT
+
+# Check for a SAMPLE_PIPELINE_PYTHON_ENV.
+if [ -n "$SAMPLE_PIPELINE_PYTHON_ENV" ]; then
+ if [ -z "$ZIGGY_HOME" ]; then
+ echo "SAMPLE_PIPELINE_PYTHON_ENV set but ZIGGY_HOME not set!"
+ exit 1
+ fi
+else
+ etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+ sample_home="$(dirname "$etc_dir")"
+ ZIGGY_HOME="$(dirname "$sample_home")"
+ SAMPLE_PIPELINE_PYTHON_ENV=$sample_home/build/env
+fi
+
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $SAMPLE_PIPELINE_PYTHON_ENV/bin/activate
+
+# Get the location of the environment's site packages directory
+SITE_PKGS=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Use the environment's Python to run the image-averaging Python script
+python3 $SITE_PKGS/major_tom/averaging.py
+
+# capture the Python exit code and pass it to the caller as the script's exit code
+exit $?
\ No newline at end of file
diff --git a/sample-pipeline/src/main/sh/flip.sh b/sample-pipeline/src/main/sh/flip.sh
new file mode 100755
index 0000000..dbf23f0
--- /dev/null
+++ b/sample-pipeline/src/main/sh/flip.sh
@@ -0,0 +1,48 @@
+#! /bin/bash
+#
+# Shell script that runs the left-right and up-down pipeline module.
+#
+# The default assumption is that the PIPELINE_CONFIG_PATH environment variable points
+# at the etc/sample.properties file in the sample-pipeline directory. In this case,
+# the known location of that file allows the script to find everything else it needs
+# relative to that location.
+#
+# Alternately, the user may specify two environment variables:
+# SAMPLE_PIPELINE_PYTHON_ENV specifies the location of the Python
+# environment constructed by build-env.sh.
+# ZIGGY_HOME specifies the top-level Ziggy directory.
+#
+# Note that if you point PIPELINE_CONFIG_PATH at a different file (if, for example,
+# you copy the file that ships with Ziggy to a different location so you can edit it
+# without running afoul of Git change tracking), you will need to define ZIGGY_HOME
+# and SAMPLE_PIPELINE_PYTHON_ENV variables!
+#
+# Author: PT
+
+# Check for a SAMPLE_PIPELINE_PYTHON_ENV.
+if [ -n "$SAMPLE_PIPELINE_PYTHON_ENV" ]; then
+ if [ -z "$ZIGGY_HOME" ]; then
+ echo "SAMPLE_PIPELINE_PYTHON_ENV set but ZIGGY_HOME not set!"
+ exit 1
+ fi
+else
+ etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+ sample_home="$(dirname "$etc_dir")"
+ ZIGGY_HOME="$(dirname "$sample_home")"
+ SAMPLE_PIPELINE_PYTHON_ENV=$sample_home/build/env
+fi
+
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $SAMPLE_PIPELINE_PYTHON_ENV/bin/activate
+
+# Get the location of the environment's site packages directory
+SITE_PKGS=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Use the environment's Python to run the flipper Python script
+python3 $SITE_PKGS/major_tom/flip.py
+
+# capture the Python exit code and pass it to the caller as the script's exit code
+exit $?
\ No newline at end of file
diff --git a/sample-pipeline/src/main/sh/permuter.sh b/sample-pipeline/src/main/sh/permuter.sh
new file mode 100755
index 0000000..81db186
--- /dev/null
+++ b/sample-pipeline/src/main/sh/permuter.sh
@@ -0,0 +1,48 @@
+#! /bin/bash
+#
+# Shell script that runs the color-permuting pipeline module.
+#
+# The default assumption is that the PIPELINE_CONFIG_PATH environment variable points
+# at the etc/sample.properties file in the sample-pipeline directory. In this case,
+# the known location of that file allows the script to find everything else it needs
+# relative to that location.
+#
+# Alternately, the user may specify two environment variables:
+# SAMPLE_PIPELINE_PYTHON_ENV specifies the location of the Python
+# environment constructed by build-env.sh.
+# ZIGGY_HOME specifies the top-level Ziggy directory.
+#
+# Note that if you point PIPELINE_CONFIG_PATH at a different file (if, for example,
+# you copy the file that ships with Ziggy to a different location so you can edit it
+# without running afoul of Git change tracking), you will need to define ZIGGY_HOME
+# and SAMPLE_PIPELINE_PYTHON_ENV variables!
+#
+# Author: PT
+
+# Check for a SAMPLE_PIPELINE_PYTHON_ENV.
+if [ -n "$SAMPLE_PIPELINE_PYTHON_ENV" ]; then
+ if [ -z "$ZIGGY_HOME" ]; then
+ echo "SAMPLE_PIPELINE_PYTHON_ENV set but ZIGGY_HOME not set!"
+ exit 1
+ fi
+else
+ etc_dir="$(dirname "$PIPELINE_CONFIG_PATH")"
+ sample_home="$(dirname "$etc_dir")"
+ ZIGGY_HOME="$(dirname "$sample_home")"
+ SAMPLE_PIPELINE_PYTHON_ENV=$sample_home/build/env
+fi
+
+# We're about to activate the environment, so we should make sure that the environment
+# gets deactivated at the end of script execution.
+trap 'deactivate' EXIT
+
+source $SAMPLE_PIPELINE_PYTHON_ENV/bin/activate
+
+# Get the location of the environment's site packages directory
+SITE_PKGS=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
+
+# Use the environment's Python to run the permuter Python script
+python3 $SITE_PKGS/major_tom/permuter.py
+
+# capture the Python exit code and pass it to the caller as the script's exit code
+exit $?
\ No newline at end of file
diff --git a/script-plugins/copy.gradle b/script-plugins/copy.gradle
new file mode 100644
index 0000000..29faf84
--- /dev/null
+++ b/script-plugins/copy.gradle
@@ -0,0 +1,41 @@
+task copySh(type: Copy) {
+ from(fileTree("src/main/sh").files) {
+ include('*.sh')
+ rename('(.+)\\.sh$', '$1')
+ fileMode(0755)
+ }
+ into "$buildDir/bin"
+}
+
+assemble.dependsOn copySh
+
+task copyPerl(type: Copy) {
+ from(fileTree("src/main/perl").files) {
+ include('*.pl')
+ rename('(.+)\\.pl$', '$1')
+ fileMode(0755)
+ }
+ into "$buildDir/bin"
+}
+
+assemble.dependsOn copyPerl
+
+task copyEtc(type: Copy) {
+ from("$projectDir/etc")
+ into "$buildDir/etc"
+}
+
+assemble.dependsOn copyEtc
+
+task copyLibs(type: Copy) {
+ from(configurations.runtime) {
+ fileMode(0644)
+ }
+ from("$projectDir/src/main/python") {
+ include "hdf5mi/**"
+ include "zigutils/**"
+ }
+ into "$buildDir/libs"
+}
+
+assemble.dependsOn copyLibs
diff --git a/script-plugins/database-schemas.gradle b/script-plugins/database-schemas.gradle
new file mode 100644
index 0000000..b6eb6e5
--- /dev/null
+++ b/script-plugins/database-schemas.gradle
@@ -0,0 +1,75 @@
+// The logging.captureStandard* settings squelch Hibernate's output. Use gradle --info to see it.
+
+task generateHsqldbCreateScript(type: JavaExec, dependsOn: [jar, copyLibs]) {
+ inputs.dir "$projectDir/build/libs"
+ outputs.dir "$projectDir/build/schema"
+ outputs.file "$projectDir/build/schema/ddl.hsqldb-create.sql"
+
+ main "gov.nasa.ziggy.services.database.ZiggySchemaExport"
+ classpath fileTree(dir: "$projectDir/build/libs", include: "*.jar")
+ jvmArgs "-Dhibernate.connection.driver_class=org.hsqldb.jdbcDriver",
+ "-Dhibernate.dialect=org.hibernate.dialect.HSQLDialect", "-Dhibernate.jdbc.batch_size=0",
+ "-Dlog4j2.configurationFile=$rootDir/etc/log4j2.xml"
+ args "--create", "--output=$projectDir/build/schema/ddl.hsqldb-create.sql"
+
+ logging.captureStandardOutput LogLevel.INFO
+ logging.captureStandardError LogLevel.INFO
+}
+
+test.dependsOn generateHsqldbCreateScript
+assemble.dependsOn generateHsqldbCreateScript
+
+task generateHsqldbDropScript(type: JavaExec, dependsOn: [jar, copyLibs]) {
+ inputs.dir "$projectDir/build/libs"
+ outputs.dir "$projectDir/build/schema"
+ outputs.file "$projectDir/build/schema/ddl.hsqldb-drop.sql"
+
+ main "gov.nasa.ziggy.services.database.ZiggySchemaExport"
+ classpath fileTree(dir: "$projectDir/build/libs", include: "*.jar")
+ jvmArgs "-Dhibernate.connection.driver_class=org.hsqldb.jdbcDriver",
+ "-Dhibernate.dialect=org.hibernate.dialect.HSQLDialect", "-Dhibernate.jdbc.batch_size=0",
+ "-Dlog4j2.configurationFile=$rootDir/etc/log4j2.xml"
+ args "--drop", "--output=$projectDir/build/schema/ddl.hsqldb-drop.sql"
+
+ logging.captureStandardOutput LogLevel.INFO
+ logging.captureStandardError LogLevel.INFO
+}
+
+test.dependsOn generateHsqldbDropScript
+assemble.dependsOn generateHsqldbDropScript
+
+task generatePostgresqlCreateScript(type: JavaExec, dependsOn: [jar, copyLibs]) {
+ inputs.dir "$projectDir/build/libs"
+ outputs.dir "$projectDir/build/schema"
+ outputs.file "$projectDir/build/schema/ddl.postgresql-create.sql"
+
+ main "gov.nasa.ziggy.services.database.ZiggySchemaExport"
+ classpath fileTree(dir: "$projectDir/build/libs", include: "*.jar")
+ jvmArgs "-Dhibernate.connection.driver_class=org.postgresql.Driver",
+ "-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect",
+ "-Dlog4j2.configurationFile=$rootDir/etc/log4j2.xml"
+ args "--create", "--output=$projectDir/build/schema/ddl.postgresql-create.sql"
+
+ logging.captureStandardOutput LogLevel.INFO
+ logging.captureStandardError LogLevel.INFO
+}
+
+assemble.dependsOn generatePostgresqlCreateScript
+
+task generatePostgresqlDropScript(type: JavaExec, dependsOn: [jar, copyLibs]) {
+ inputs.dir "$projectDir/build/libs"
+ outputs.dir "$projectDir/build/schema"
+ outputs.file "$projectDir/build/schema/ddl.postgresql-drop.sql"
+
+ main "gov.nasa.ziggy.services.database.ZiggySchemaExport"
+ classpath fileTree(dir: "$projectDir/build/libs", include: "*.jar")
+ jvmArgs "-Dhibernate.connection.driver_class=org.postgresql.Driver",
+ "-Dhibernate.dialect=org.hibernate.dialect.PostgreSQLDialect",
+ "-Dlog4j2.configurationFile=$rootDir/etc/log4j2.xml"
+ args "--drop", "--output=$projectDir/build/schema/ddl.postgresql-drop.sql"
+
+ logging.captureStandardOutput LogLevel.INFO
+ logging.captureStandardError LogLevel.INFO
+}
+
+assemble.dependsOn generatePostgresqlDropScript
diff --git a/script-plugins/eclipse.gradle b/script-plugins/eclipse.gradle
new file mode 100644
index 0000000..a0357a1
--- /dev/null
+++ b/script-plugins/eclipse.gradle
@@ -0,0 +1,40 @@
+// Generates the Eclipse .classpath and .project files.
+eclipse {
+ classpath {
+ defaultOutputDir = file("$buildDir/eclipse")
+ // outputBaseDir = file("$buildDir/eclipse")
+ downloadSources = false
+ downloadJavadoc = false
+
+ // Gradle 4.4 now specifies all of the output directories, but puts
+ // them in the Eclipse default of "bin". There is a feature request
+ // to add classpath.outputBaseDir that has the same syntax and effect
+ // as the now-useless defaultOutputDir. In the meantime, update the
+ // path manually.
+ file.whenMerged {
+ entries.each { entry ->
+ if (entry.kind == "src" && entry.hasProperty("output")) {
+ // The use of $buildDir does not return build.
+ entry.output = "build/eclipse"
+ }
+ }
+ }
+
+ // Add native libraries for HDF5.
+ file.whenMerged { classpath ->
+ def hdf5 = classpath.entries.findResult { entry ->
+ if (entry.kind == 'lib' && entry.path.contains('jarhdf5')) {
+ return entry
+ }
+ }
+
+ // TODO Get the following pseudocode to work to avoid hardcoding path
+ //File jar = findFileInConfiguration(configurations.runtime, 'jarhdf5-1.12.2')
+ //hdf5.setNativeLibraryLocation(jar.getParent())
+ hdf5.setNativeLibraryLocation("$ziggyDependencies/lib")
+ }
+ }
+}
+
+// Avoid duplicate classpath entries.
+tasks.eclipse.dependsOn(cleanEclipse)
diff --git a/script-plugins/gcc.gradle b/script-plugins/gcc.gradle
new file mode 100644
index 0000000..c6ddf4e
--- /dev/null
+++ b/script-plugins/gcc.gradle
@@ -0,0 +1,13 @@
+// Default C / C++ options for regular and mexfile builds that can be
+// overridden in build.gradle files.
+
+ext.defaultCppCompileOptions = ["Wall", "fPIC", "std=c++11"];
+ext.defaultCppReleaseOptimizations = ["O2", "DNDEBUG", "g"];
+ext.defaultCppDebugOptimizations = ["Og", "g"];
+ext.defaultCppMexCompileOptions = ["Wall", "fPIC", "std=c++11", "D_GNU_SOURCE", "fexceptions",
+ "fno-omit-frame-pointer", "pthread", "fno-reorder-blocks", "fstack-protector-all",
+ "fpermissive"]
+ext.defaultCppMexReleaseOptimizations = ["O2", "DNDEBUG", "g"]
+ext.defaultCppMexDebugOptimizations = ["Og", "g"]
+ext.defaultCppMexLinkOptions = ["lstdc++"]
+
diff --git a/script-plugins/hdf5.gradle b/script-plugins/hdf5.gradle
new file mode 100644
index 0000000..cf09e88
--- /dev/null
+++ b/script-plugins/hdf5.gradle
@@ -0,0 +1,17 @@
+task copyHdf5Lib(type: Copy) {
+ from(file("$ziggyDependencies/lib")) {
+ include "*hdf5*"
+ }
+ into "$buildDir/lib"
+}
+
+assemble.dependsOn copyHdf5Lib
+
+task copyHdf5Libs(type: Copy) {
+ from(file("$ziggyDependencies/lib")) {
+ include "jarhdf5-1.12.2.jar"
+ }
+ into "$buildDir/libs"
+}
+
+assemble.dependsOn copyHdf5Libs
diff --git a/script-plugins/matlab.gradle b/script-plugins/matlab.gradle
new file mode 100644
index 0000000..c5605c5
--- /dev/null
+++ b/script-plugins/matlab.gradle
@@ -0,0 +1,19 @@
+ext.generatedSrcMatlabDir = file("$buildDir/src/main/matlab")
+
+task makeMatlabDirs {
+ outputs.dir generatedSrcMatlabDir
+ doLast() {
+ generatedSrcMatlabDir.mkdirs()
+ }
+}
+
+assemble.dependsOn makeMatlabDirs
+
+// All MATLAB source files. Use sourceSets.main.allJava for all Java files.
+ext.allMatlab = fileTree(dir: "src/main/matlab").matching {
+ include "**/*.m"
+}
+
+ext.allMatlabProxy = fileTree(dir: "$buildDir/src/main/matlab").matching {
+ include "**/*.m"
+}
diff --git a/script-plugins/misc.gradle b/script-plugins/misc.gradle
new file mode 100644
index 0000000..88a1ff6
--- /dev/null
+++ b/script-plugins/misc.gradle
@@ -0,0 +1,22 @@
+// Show all dependencies.
+task allDeps(type: DependencyReportTask) {}
+
+// Generate the Ziggy version information.
+import gov.nasa.ziggy.buildutil.ZiggyVersionGenerator
+
+task ziggyVersion(type: ZiggyVersionGenerator) {
+ outputFile = file("$buildDir/src/main/java/gov/nasa/ziggy/util/ZiggyVersion.java")
+ // TODO Add a development property to suppress false upToDateWhen setting
+ // It causes the world to recompile.
+ outputs.upToDateWhen{false}
+}
+
+sourceSets.main.java.srcDir file("$buildDir/src/main/java")
+compileJava.dependsOn ziggyVersion
+
+clean.doFirst() {
+ File workerPidFile = new File("build/bin/worker.pid");
+ if (workerPidFile.isFile()) {
+ throw new GradleException("Can't clean while cluster is running (see build/bin/worker.pid)")
+ }
+}
diff --git a/script-plugins/test.gradle b/script-plugins/test.gradle
new file mode 100644
index 0000000..d0b0fa7
--- /dev/null
+++ b/script-plugins/test.gradle
@@ -0,0 +1,22 @@
+/**
+ * testprog is used to test external process control. testprog has the
+ * advantage over something like /bin/true that it can run for a specified
+ * wall clock time, crash and return different error codes.
+ */
+task generateTestProg(type: Exec) {
+ def binDir = file("$buildDir/bin")
+
+ inputs.file "$projectDir/src/test/cpp/ExternalProcess/Makefile"
+ inputs.file "$projectDir/src/test/cpp/ExternalProcess/testprog.cpp"
+ outputs.file "$binDir/testprog"
+
+ workingDir = "$projectDir/src/test/cpp/ExternalProcess"
+ commandLine = [ "sh", "-c", "make -e" ]
+ System.setProperty("OUTDIR", "$binDir")
+}
+
+compileTestJava.dependsOn generateTestProg
+
+clean.doLast() {
+ file("$projectDir/src/test/cpp/ExternalProcess/testprog.o").delete()
+}
diff --git a/script-plugins/wrapper.gradle b/script-plugins/wrapper.gradle
new file mode 100644
index 0000000..f4b4086
--- /dev/null
+++ b/script-plugins/wrapper.gradle
@@ -0,0 +1,17 @@
+task copyWrapper(type: Copy) {
+ from(file("$ziggyDependencies/bin")) {
+ include "wrapper"
+ }
+ into "$buildDir/bin"
+}
+
+assemble.dependsOn copyWrapper
+
+task copyWrapperLibs(type: Copy) {
+ from(file("$ziggyDependencies/lib")) {
+ include "libwrapper.*"
+ }
+ into "$buildDir/lib"
+}
+
+assemble.dependsOn copyWrapperLibs
diff --git a/script-plugins/xml-schemas.gradle b/script-plugins/xml-schemas.gradle
new file mode 100644
index 0000000..8b1eeb3
--- /dev/null
+++ b/script-plugins/xml-schemas.gradle
@@ -0,0 +1,10 @@
+// Generate XML schemas.
+task generateXmlSchemas(type: JavaExec, dependsOn: compileJava) {
+ outputs.dir "$projectDir/build/schema/xml"
+
+ main "gov.nasa.ziggy.pipeline.xml.XmlSchemaExporter"
+ classpath fileTree(dir: "$projectDir/build/libs", include: "*.jar")
+}
+
+test.dependsOn generateXmlSchemas
+assemble.dependsOn generateXmlSchemas
diff --git a/script-plugins/ziggy-libraries.gradle b/script-plugins/ziggy-libraries.gradle
new file mode 100644
index 0000000..22282be
--- /dev/null
+++ b/script-plugins/ziggy-libraries.gradle
@@ -0,0 +1,23 @@
+// Build the libziggy shared object.
+import gov.nasa.ziggy.buildutil.ZiggyCpp
+import gov.nasa.ziggy.buildutil.ZiggyCppMex
+
+task buildLibZiggy(type: ZiggyCpp) {
+ outputName = "ziggy"
+ outputType = "static"
+ cppFilePaths = ["$projectDir/src/main/cpp/libziggy"]
+ includeFilePaths = ["$projectDir/src/main/cpp/libziggy"]
+}
+
+assemble.dependsOn buildLibZiggy
+
+// Build the libziggymi shared object.
+task buildLibZiggyMi(type: ZiggyCpp) {
+ outputName = "ziggymi"
+ outputType = "static"
+ cppFilePaths = ["$projectDir/src/main/cpp/libziggymi"]
+ includeFilePaths = ["$projectDir/src/main/cpp/libziggymi", "$projectDir/src/main/cpp/libziggy",
+ "$ziggyDependencies/include"]
+}
+
+assemble.dependsOn buildLibZiggyMi
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+
diff --git a/src/main/c/linux-cache-control.c b/src/main/c/linux-cache-control.c
new file mode 100644
index 0000000..3a1415d
--- /dev/null
+++ b/src/main/c/linux-cache-control.c
@@ -0,0 +1,33 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define DROP_CACHE_FILE "/proc/sys/vm/drop_caches"
+// echo 3 > /proc/sys/vm/drop_caches
+int main(int argc, char **argv) {
+
+ int fd = open(DROP_CACHE_FILE, O_WRONLY);
+ if (fd < 0) {
+ char buf[255];
+ snprintf(buf, 255, "Error while opening %s\n", DROP_CACHE_FILE);
+ perror(buf);
+ return -1;
+ }
+
+ char dropLevel[] = "3\n";
+ ssize_t nBytesWritten = write(fd, dropLevel, 2);
+ if (nBytesWritten != 2) {
+ char buf[255];
+ snprintf(buf, 255, "Error while writing to %s\n", DROP_CACHE_FILE);
+ perror(buf);
+ close(fd);
+ return -2;
+ }
+ close(fd);
+ return 0;
+
+}
+
diff --git a/src/main/cpp/libziggy/ziggy_exceptions.cpp b/src/main/cpp/libziggy/ziggy_exceptions.cpp
new file mode 100644
index 0000000..f7f8abc
--- /dev/null
+++ b/src/main/cpp/libziggy/ziggy_exceptions.cpp
@@ -0,0 +1,19 @@
+#include "ziggy_exceptions.h"
+
+using namespace std;
+
+void ZiggyException::exceptionMessage(ZiggyException exception, const char* file, const char* functionName,
+ int line, const char* exceptionName, const char* msg) {
+
+ cerr << file << "(" << line << "): exception in function " << functionName << endl;
+ cerr << "Exception: " << exceptionName << endl;
+ cerr << "What: " << exception.what() << endl;
+ if (msg != NULL) {
+ cerr << "Message: " << msg << endl;
+ }
+
+ // now that the output is complete, we can throw the actual error
+ throw exception;
+}
+
+
diff --git a/src/main/cpp/libziggy/ziggy_exceptions.h b/src/main/cpp/libziggy/ziggy_exceptions.h
new file mode 100644
index 0000000..16ec35c
--- /dev/null
+++ b/src/main/cpp/libziggy/ziggy_exceptions.h
@@ -0,0 +1,85 @@
+
+#ifndef _ziggy_exceptions_h_
+#define _ziggy_exceptions_h_ 1
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//These includes are for the backtrace functionality
+#include
+#include
+#include
+
+#define MAX_STACK_DEPTH 128
+#define RUN_TIME_STACK_TRACE \
+{ \
+ void* stackPointers[MAX_STACK_DEPTH]; \
+ uint32_t traceCount = backtrace (stackPointers, MAX_STACK_DEPTH); \
+ char** symbols = backtrace_symbols(stackPointers, traceCount); \
+ if (symbols != nullptr) { \
+ for (uint32_t frameIndex=0; frameIndex < traceCount; frameIndex++) { \
+ std::cerr << symbols[frameIndex] << std::endl; \
+ } \
+ free(symbols); \
+ } \
+}
+
+/**
+ * Concrete base class for exceptions.
+ */
+class ZiggyException : public virtual std::exception {
+public:
+ ZiggyException(const char* whatMessage_p) : whatMessage_m(whatMessage_p) {}
+ ZiggyException(const std::string& whatMessage_p) : whatMessage_m(whatMessage_p) {}
+ ZiggyException(const ZiggyException& src) = default;
+
+ virtual const char* what() const noexcept {
+ return whatMessage_m.c_str();
+ }
+
+
+ virtual ~ZiggyException() {
+ }
+
+ static void exceptionMessage(ZiggyException exception, const char* file, const char* function, int line,
+ const char* name, const char* msg);
+
+private:
+ std::string whatMessage_m;
+};
+
+
+/**
+ * Base class for I/O errors.
+ */
+class IoException : public ZiggyException {
+public:
+ IoException(const char* whatMessage_p) : ZiggyException(whatMessage_p) {}
+ IoException(const std::string& whatMessage_p) : ZiggyException(whatMessage_p) {}
+ IoException(const IoException& src) = default;
+
+ virtual ~IoException() {
+ }
+};
+
+#define ZIGGY_THROW(ExceptionTypeName) \
+{ \
+ ZiggyException::exceptionMessage(ExceptionTypeName(#ExceptionTypeName), __FILE__, __FUNCTION__, __LINE__, \
+ #ExceptionTypeName, NULL); \
+\}
+
+
+#define ZIGGY_THROW_MSG(ExceptionTypeName, Msg) \
+{ \
+ ZiggyException::exceptionMessage(ExceptionTypeName(#ExceptionTypeName), __FILE__, __FUNCTION__, __LINE__, \
+ #ExceptionTypeName, Msg); \
+}
+
+#endif
+
diff --git a/src/main/cpp/libziggy/ziggy_signal_handler.h b/src/main/cpp/libziggy/ziggy_signal_handler.h
new file mode 100644
index 0000000..93d33c5
--- /dev/null
+++ b/src/main/cpp/libziggy/ziggy_signal_handler.h
@@ -0,0 +1,21 @@
+#ifndef _ziggy_signal_handler_h_
+#define _ziggy_signal_handler_h_ 1
+
+#include "ziggy_exceptions.h"
+#include
+
+/**
+ * This is the signal handler that gets registered using signal(). It's exposed here in case.
+ * you don't want to use register_print_stack_on_signal() and want to register it yourself.
+ * This function will attempt to cause the process to exit() upon completion.
+ */
+extern "C" void print_stack_trace_signal_handler(int32_t signalNumber);
+
+/**
+ * Attempts to print a stack trace to standard error when one of the following signals is raised:
+ * SIGABRT, SIGSEGV, SIGBUS, SIGILL, SIGFPE.
+ */
+extern "C" void register_print_stack_on_signal();
+
+#endif //_ziggy_signal_handler_h_
+
diff --git a/src/main/cpp/libziggy/ziggy_signal_handler_cpp b/src/main/cpp/libziggy/ziggy_signal_handler_cpp
new file mode 100644
index 0000000..f276dca
--- /dev/null
+++ b/src/main/cpp/libziggy/ziggy_signal_handler_cpp
@@ -0,0 +1,38 @@
+#include "ziggy_signal_handler.h"
+
+#include
+#include
+
+using namespace std;
+
+static const char* signalToStr(uint32_t signalNumber) {
+ switch (signalNumber) {
+ case SIGABRT: return "SIGABRT";
+ case SIGSEGV: return "SIGSEGV";
+ case SIGBUS: return "SIGBUS";
+ case SIGILL: return "SIGILL";
+ case SIGFPE: return "SIGFPE";
+ default:
+ return "unknown signal";
+ }
+}
+
+ extern "C" void print_stack_trace_signal_handler(int32_t signalNumber) {
+ cerr << "Caught signal " << signalToStr(signalNumber) << "; attempting to print stack trace..." << endl;
+ RUN_TIME_STACK_TRACE
+ exit(-1);
+ //return -1;
+}
+
+extern "C" void register_print_stack_on_signal() {
+ cerr << "Registering signal handlers" << endl;
+
+ signal(SIGABRT, print_stack_trace_signal_handler);
+ signal(SIGSEGV, print_stack_trace_signal_handler);
+ signal(SIGBUS, print_stack_trace_signal_handler);
+ signal(SIGILL, print_stack_trace_signal_handler);
+ signal(SIGFPE, print_stack_trace_signal_handler);
+
+ cerr << "DONE Registering signal handlers" << endl;
+}
+
diff --git a/src/main/cpp/libziggymi/Hdf5Interface.cpp b/src/main/cpp/libziggymi/Hdf5Interface.cpp
new file mode 100644
index 0000000..054ef95
--- /dev/null
+++ b/src/main/cpp/libziggymi/Hdf5Interface.cpp
@@ -0,0 +1,450 @@
+/*
+ * Hdf5Interface.cpp
+ *
+ * Provides a data structure that contains the most important HDF5 ID values
+ * (group, dataset, dataspace, datatype, name) and tools for manipulating the
+ * HDF5 objects referred to by these values. Note that all these methods are
+ * intended for use in auto-generated code that's only used to read and write
+ * HDF5 files that are organized in a particular way, hence there is basically
+ * no error-checking in the code here because we can rely on the auto-generator
+ * to make sure that we only use the class and its methods under circumstances
+ * where we know no error will occur.
+ * Created on: May 17, 2019
+ * Author: PT
+ */
+
+#include "Hdf5Interface.h"
+
+/**
+ * Constructor. Takes the parent group (can be a file) and the name of the group to be opened.
+ * Populates the group ID, dataset ID, and dataspace ID, opening all of these. The assumption is
+ * that the group contains a dataset with the same name as the group.
+ */
+Hdf5Interface::Hdf5Interface(hid_t parentHdf5Id_p, std::string name_p) :
+ parentHdf5Id(parentHdf5Id_p), name(name_p) {
+
+ // open the group if it exists
+ hdf5GroupId = H5Gopen2(parentHdf5Id, name.c_str(), H5P_DEFAULT);
+ datatypeNeedsToBeClosed = false;
+
+ // capture the contents of the group
+ if (!H5Aexists(hdf5GroupId, EMPTY_FIELD_ATT_NAME) &&
+ !H5Aexists(hdf5GroupId, OBJECT_ARRAY_ATT_NAME)) {
+ datasetId = H5Dopen(hdf5GroupId, name.c_str(), H5P_DEFAULT);
+ dataspaceId = H5Dget_space(datasetId);
+ } else {
+ datasetId = -1 ;
+ dataspaceId = -1;
+ }
+
+}
+
+/**
+ * Constructor used for cases in which HDF5 arrays are to be WRITTEN. Note that this
+ * constructor is lightweight but doesn't do nearly enough of the setup that is needed
+ * to make a usable Hdf5Interface object for writing, to do that use the static method
+ * Hdf5InterfaceForWriting.
+ */
+Hdf5Interface::Hdf5Interface(hid_t parentHdf5Id_p, std::string name_p, hid_t datatypeId_p) :
+ parentHdf5Id(parentHdf5Id_p), name(name_p), datatypeId(datatypeId_p) {
+
+ // default the group, dataspace, dataset, and datatype to -1
+ hdf5GroupId = -1;
+ dataspaceId = -1;
+ datasetId = -1;
+ datatypeNeedsToBeClosed = false;
+
+}
+
+/**
+ * Static factory method that sets up an Hdf5Interface object for writing.
+ */
+Hdf5Interface* Hdf5Interface::Hdf5InterfaceForWriting(hid_t parentHdf5Id_p,
+ std::string name_p, hid_t datatypeId_p, hsize_t nDims, hsize_t* dims_p, int32_t typeInt,
+ bool booleanArray, int32_t fieldOrder) {
+
+ hsize_t* dims;
+ hsize_t scalarDims[1];
+ // handle the case in which the dims are null but nDims is 1
+ if (nDims == 1 && dims_p == NULL) {
+ dims = scalarDims;
+ dims[0] = 1;
+ } else {
+ dims = dims_p;
+ }
+
+ // construct the object
+ Hdf5Interface* hdf5Interface = new Hdf5Interface(parentHdf5Id_p, name_p, datatypeId_p);
+
+ // create the HDF5 group
+ hdf5Interface->hdf5GroupId = H5Gcreate(hdf5Interface->parentHdf5Id,
+ hdf5Interface->name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+
+ // set the field order
+ hdf5Interface->addScalarInt32Attribute(FIELD_ORDER_ATT_NAME, fieldOrder);
+
+ // if this is an empty array, set that up now
+ if (nDims == 1 && dims[0] <= 0) {
+ hdf5Interface->addMarkerAttribute(EMPTY_FIELD_ATT_NAME);
+ return hdf5Interface;
+ }
+
+ // populate the typeInt attribute -- this is a means of storing the data type that
+ // is independent of the HDF5 type integers, which can vary based on the version of
+ // the HDF5 library you use
+ hdf5Interface->addScalarInt32Attribute(FIELD_DATA_TYPE_ATT_NAME, typeInt);
+
+ // if this is a scalar or array of Persistable objects, populate the appropriate
+ // attributes and return
+ if (hdf5Interface->datatypeId == H5T_OPAQUE) {
+ if (nDims > 1 || dims[0] > 1) { // object array case
+ hdf5Interface->addMarkerAttribute(OBJECT_ARRAY_ATT_NAME);
+
+ hid_t attributeSpace = H5Screate_simple(1, &nDims, NULL);
+ hid_t attributeId = H5Acreate(hdf5Interface->hdf5GroupId, OBJECT_ARRAY_DIMS_ATT_NAME,
+ H5T_NATIVE_INT64, attributeSpace, H5P_DEFAULT, H5P_DEFAULT);
+ H5Awrite(attributeId, H5T_NATIVE_INT64, dims);
+ H5Aclose(attributeId);
+ H5Aclose(attributeSpace);
+ }
+ return hdf5Interface;
+ }
+
+
+ // construct a property for deflation and chunking
+ hid_t deflateProperty;
+ long nElem = 1;
+ for (uint i=0 ; i= MIN_COMPRESSION_ELEMENTS && COMPRESSION_LEVEL > 0) {
+ deflateProperty = H5Pcreate(H5P_DATASET_CREATE);
+ H5Pset_chunk(deflateProperty, nDims, hdf5Interface->chunkSize(nDims, dims));
+ H5Pset_deflate(deflateProperty, COMPRESSION_LEVEL);
+ } else {
+ deflateProperty = H5Pcopy(H5P_DEFAULT);
+ }
+
+ // If this is a scalar or array of string objects, construct a data type for var-length
+ // strings and populate the appropriate attribute for the group
+ if (hdf5Interface->datatypeId == H5T_C_S1) {
+ hdf5Interface->datatypeId = H5Tcopy(hdf5Interface->datatypeId);
+ hdf5Interface->datatypeNeedsToBeClosed = true;
+ H5Tset_size(hdf5Interface->datatypeId, H5T_VARIABLE);
+ if (nDims > 1 || dims[0] > 1) {
+ hdf5Interface->addMarkerAttribute(STRING_ARRAY_ATT_NAME);
+ }
+ }
+
+ // if this was originally a boolean array that is being stored as an int8 array,
+ // because HDF5 has no native support for booleans, set the appropriate attribute now
+ if (booleanArray) {
+ hdf5Interface->addMarkerAttribute(BOOLEAN_ARRAY_ATT_NAME);
+ }
+
+ // construct dataspace and dataset, including any compression desirements
+ hdf5Interface->dataspaceId = H5Screate_simple(nDims, dims, NULL);
+ hdf5Interface->datasetId = H5Dcreate(hdf5Interface->hdf5GroupId,
+ hdf5Interface->name.c_str(), hdf5Interface->datatypeId,
+ hdf5Interface->dataspaceId, H5P_DEFAULT, deflateProperty, H5P_DEFAULT);
+
+ return hdf5Interface;
+}
+
+hsize_t* Hdf5Interface::chunkSize(hsize_t nDims, hsize_t* dims) {
+
+ hsize_t* cSize = new hsize_t[nDims];
+ for (uint i=0 ; i 0, we are done with this
+ // activity and can return
+ if (nRows == 0) {
+ cSize[iDim] = 1;
+ } else {
+ cSize[iDim] = nRows;
+ break;
+ }
+ }
+ return cSize;
+}
+
+/**
+ * Destructor. Closes the dataspace, dataset, and group.
+ */
+Hdf5Interface::~Hdf5Interface() {
+ if (dataspaceId > 0) {
+ H5Sclose(dataspaceId);
+ }
+ if (datasetId > 0) {
+ H5Dclose(datasetId);
+ }
+ if (datatypeNeedsToBeClosed) {
+ H5Tclose(datatypeId);
+ }
+ if (hdf5GroupId > 0) {
+ H5Gclose(hdf5GroupId);
+ }
+}
+
+/**
+ * Gets the dimensions of a persistable array; these are stored as an attribute of the
+ * group of the top-level HDF5 group for the array.
+ */
+long* Hdf5Interface::getPersistableArrayDims() const {
+
+ // handle case of the array being empty
+ htri_t hasEmptyAttribute = H5Aexists(hdf5GroupId, EMPTY_FIELD_ATT_NAME);
+ if (hasEmptyAttribute > 0) {
+ long* emptyDims = new long[1];
+ emptyDims[0] = 0;
+ return emptyDims;
+ }
+ hid_t attributeId = H5Aopen(hdf5GroupId, OBJECT_ARRAY_DIMS_ATT_NAME, H5P_DEFAULT);
+ hid_t attributeDataspaceId = H5Aget_space(attributeId);
+ int nDims = H5Sget_simple_extent_ndims(attributeDataspaceId);
+ long* dims = new long[nDims];
+ H5Aread(attributeId, H5T_NATIVE_INT64, dims);
+ H5Sclose(attributeDataspaceId);
+ H5Aclose(attributeId);
+ return dims;
+}
+
+std::string Hdf5Interface::readString() {
+ char * readBuffer;
+ if (datasetId == -1) {
+ readBuffer = (char*)"";
+ } else {
+ hid_t memtype = H5Tcopy(H5T_C_S1);
+ H5Tset_size(memtype, H5T_VARIABLE);
+ H5Dread(datasetId, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &readBuffer);
+ H5Tclose(memtype);
+ }
+ std::string retval = readBuffer;
+ return retval;
+}
+
+void Hdf5Interface::writeString(std::string s) {
+ const char* writeBuffer = s.c_str();
+ H5Dwrite(datasetId, datatypeId, H5S_ALL, H5S_ALL, H5P_DEFAULT, &writeBuffer);
+}
+
+hsize_t* Hdf5Interface::getDims() const {
+
+ // determine the size of the dataspace
+ int nDims = this->getNDims();
+ hsize_t* dims = new hsize_t[nDims];
+ H5Sget_simple_extent_dims(dataspaceId, dims, NULL);
+ return dims;
+
+}
+
+int Hdf5Interface::getNDims() const {
+ // determine the size of the dataspace
+ int nDims = H5Sget_simple_extent_ndims(dataspaceId);
+ return nDims;
+}
+
+/**
+ * Casts a boolean vector to int8. This is necessary because HDF5 does not support
+ * boolean data types, so they are transported from the worker to the application
+ * and back as int8's.
+ */
+std::vector Hdf5Interface::boolToInt8Vector(std::vector boolVector) {
+
+ if (boolVector.empty()) {
+ return std::vector();
+ }
+
+ std::vector int8Vector(boolVector.size());
+ for (uint i=0 ; i Hdf5Interface::int8ToBoolVector(std::vector int8Vector) {
+
+ if (int8Vector.empty()) {
+ return std::vector();
+ }
+
+ std::vector boolVector(int8Vector.size());
+ for (uint i=0 ; iaddMarkerAttribute(PARALLEL_ARRAY_ATT_NAME);
+ }
+ obj.writeHdf5(hdf5Interface->get_hdf5GroupId());
+ H5Gclose(hdf5Interface->get_hdf5GroupId());
+}
+
+void Hdf5Interface::addMarkerAttribute(char* attributeName) {
+ hid_t attributeSpace = H5Screate(H5S_SCALAR);
+ hid_t attributeId = H5Acreate(hdf5GroupId, attributeName,
+ H5T_NATIVE_INT8, attributeSpace, H5P_DEFAULT, H5P_DEFAULT);
+ H5Aclose(attributeId);
+ H5Sclose(attributeSpace);
+}
+
+void Hdf5Interface::addScalarInt32Attribute(char* attributeName, int32_t attributeValue) {
+ hid_t attributeSpace = H5Screate(H5S_SCALAR);
+ hid_t attributeId = H5Acreate(hdf5GroupId, attributeName,
+ H5T_NATIVE_INT32, attributeSpace, H5P_DEFAULT, H5P_DEFAULT);
+ H5Awrite(attributeId, H5T_NATIVE_INT32, &attributeValue);
+ H5Aclose(attributeId);
+ H5Sclose(attributeSpace);
+}
+
+
+std::vector Hdf5Interface::readStringArray() {
+
+ if (datasetId == -1) {
+ return std::vector();
+ }
+
+ // determine the size of the return array
+ int nDims = this->getNDims();
+ hsize_t* dims = this->getDims();
+ hsize_t numel = 1;
+ for (int iDim=0 ; iDim stringVector(charArray, charArray + numel);
+ return stringVector;
+
+}
+
+void Hdf5Interface::writeStringArray(std::vector content) {
+
+ // transfer the contents of the string vector to an array of char* objects
+ int nelem = content.size();
+ char* contentArray[nelem];
+ for (int i=0 ; i(content.at(i).c_str());
+ }
+
+ // write to HDF5
+ H5Dwrite(datasetId, datatypeId, H5S_ALL, H5S_ALL, H5P_DEFAULT, contentArray);
+
+}
+
+void Hdf5Interface::writeStringVector(hsize_t* offset, std::vector content) {
+
+ // determine the size of the dataspace
+ int nDims = this->getNDims();
+ hsize_t* dims = this->getDims();
+
+ // set the hyperslab parameters -- we are taking a slab that is 1 unit in each dimension
+ // except for the last dimension, which is the size of the array in that dimension. For
+ // example, if the data is 3 x 4 x 5, each block will be 1 x 1 x 5.
+ hsize_t stride[nDims];
+ hsize_t count[nDims];
+ hsize_t block[nDims];
+ for (int i=0 ; i Hdf5Interface::typeSizes = {
+ {H5T_NATIVE_INT8, 1},
+ {H5T_NATIVE_INT16, 2},
+ {H5T_NATIVE_INT32, 4},
+ {H5T_NATIVE_INT64, 8},
+ {H5T_NATIVE_FLOAT, 4},
+ {H5T_NATIVE_DOUBLE, 8},
+ {H5T_C_S1, 1},
+ {H5T_OPAQUE, 1}
+};
diff --git a/src/main/cpp/libziggymi/Hdf5Interface.h b/src/main/cpp/libziggymi/Hdf5Interface.h
new file mode 100644
index 0000000..fc6263a
--- /dev/null
+++ b/src/main/cpp/libziggymi/Hdf5Interface.h
@@ -0,0 +1,133 @@
+/*
+ * Hdf5Interface.h
+ *
+ * Created on: May 17, 2019
+ * Author: PT
+ */
+
+#ifndef HDF5INTERFACE_H_
+#define HDF5INTERFACE_H_
+
+#include "hdf5.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class Hdf5Interface {
+
+public:
+ Hdf5Interface(hid_t parentHdf5Id, std::string name);
+ ~Hdf5Interface();
+
+ template T readScalar(hid_t hdf5Type) {
+ T contents;
+ if (datasetId != -1) {
+ H5Dread(datasetId, hdf5Type, H5S_ALL, H5S_ALL, H5P_DEFAULT, &contents);
+ } else {
+ contents = (T)0;
+ }
+ return contents;
+ }
+
+ template void writeScalar(T scalarValue) {
+ H5Dwrite(datasetId, datatypeId, dataspaceId, H5S_ALL, H5P_DEFAULT, &scalarValue);
+ }
+
+ std::string readString();
+ void writeString(std::string s);
+
+ template T* readArray(hid_t hdf5Type) {
+
+ if (datasetId == -1) {
+ return NULL;
+ }
+
+ // determine the size of the return array
+ int nDims = this->getNDims();
+ hsize_t* dims = this->getDims();
+ hsize_t numel = 1;
+ for (int iDim=0 ; iDim readStringArray();
+
+
+ template void writeArray(hid_t hdf5Type, T* content) {
+
+ if (datasetId == -1) {
+ return;
+ }
+ H5Dwrite(datasetId, hdf5Type, H5S_ALL, H5S_ALL, H5P_DEFAULT, content);
+
+ }
+
+ void writeStringArray(std::vector s);
+
+ hsize_t* getDims() const;
+ int getNDims() const;
+ long* getPersistableArrayDims() const;
+
+ hid_t get_hdf5GroupId() const {return this->hdf5GroupId ; }
+ void writeStringVector(hsize_t* offset, std::vector content);
+ static std::vector int8ToBoolVector(std::vector int8Vector);
+ static std::vector boolToInt8Vector(std::vector boolVector);
+ static void openAndReadHdf5Group(Persistable& obj, hid_t hdf5ParentGroupId, std::string groupName);
+ static void createAndWriteHdf5Group(const Persistable& obj, hid_t hdf5ParentGroupId,
+ std::string groupName, int32_t fieldOrder);
+ static void createAndWriteHdf5Group(const Persistable& obj, hid_t hdf5ParentGroupId,
+ std::string groupName, bool isEmpty, int32_t fieldOrder, bool parallelFlag);
+ static void createAndWriteParallelHdf5Group(const Persistable& obj, hid_t hdf5ParentGroupId,
+ std::string groupName, bool isEmpty, int32_t fieldOrder);
+ static Hdf5Interface* Hdf5InterfaceForWriting(hid_t parentHdf5Id, std::string name,
+ hid_t datatypeId, hsize_t nDims, hsize_t* dims, int32_t typeInt,
+ bool booleanArray, int32_t fieldOrder);
+
+private:
+ hid_t parentHdf5Id;
+ hid_t hdf5GroupId;
+ std::string name;
+ hid_t datasetId;
+ hid_t dataspaceId;
+ hid_t datatypeId; // used for writing only, ignored for reading
+ bool datatypeNeedsToBeClosed;
+
+ constexpr static char* EMPTY_FIELD_ATT_NAME = (char*)"EMPTY_FIELD";
+ constexpr static char* OBJECT_ARRAY_DIMS_ATT_NAME = (char*)"STRUCT_OBJECT_ARRAY_DIMS";
+ constexpr static char* FIELD_DATA_TYPE_ATT_NAME = (char*)"DATA_TYPE";
+ constexpr static char* OBJECT_ARRAY_ATT_NAME = (char*)"STRUCT_OBJECT_ARRAY";
+ constexpr static char* STRING_ARRAY_ATT_NAME = (char*)"STRING_ARRAY";
+ constexpr static char* BOOLEAN_ARRAY_ATT_NAME = (char*)"LOGICAL_BOOLEAN_ARRAY";
+ constexpr static char* FIELD_ORDER_ATT_NAME = (char*)"FIELD_ORDER";
+ constexpr static char* PARALLEL_ARRAY_ATT_NAME = (char *)"PARALLEL_ARRAY";
+ const static int32_t HDF5_PERSISTABLE_TYPE_INT = 9;
+
+ const static int MIN_COMPRESSION_ELEMENTS = 200;
+ const static uint COMPRESSION_LEVEL = 0;
+ const static int MAX_BYTES_PER_HYPERSLAB = 2000000000;
+
+ static std::map typeSizes;
+
+ Hdf5Interface(hid_t parentHdf5Id_p, std::string name_p, hid_t datatypeId_p);
+ void addMarkerAttribute(char* attributeName);
+ void addScalarInt32Attribute(char* attributeName, int32_t attributeValue);
+ hsize_t* chunkSize(hsize_t nDims, hsize_t* dims);
+
+};
+
+#endif /* HDF5INTERFACE_H_ */
diff --git a/src/main/cpp/libziggymi/IOHandler.cpp b/src/main/cpp/libziggymi/IOHandler.cpp
new file mode 100644
index 0000000..a146d38
--- /dev/null
+++ b/src/main/cpp/libziggymi/IOHandler.cpp
@@ -0,0 +1,62 @@
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "IOHandler.h"
+#include "hdf5.h"
+
+IOHandler::IOHandler(const std::string& dir_p, const std::string& id_p, const std::string& binaryName_p) :
+ dir(dir_p), id(id_p), binaryName(binaryName_p) {
+
+ if (binaryName.rfind("/") != std::string::npos) {
+ binaryName = binaryName.substr(binaryName.rfind("/") + 1);
+ inputFilename = dir + "/" + binaryName + "-inputs-" + id + ".h5";
+ outputFilename = dir + "/" + binaryName + "-outputs-" + id + ".h5";
+ }
+}
+
+
+void IOHandler::loadInputs(Persistable& inputs) const {
+
+ time_t startTime;
+ char* inputFilenameCharArray = new char[inputFilename.length()+1];
+ strcpy(inputFilenameCharArray, inputFilename.c_str());
+ hid_t fileId = H5Fopen(inputFilenameCharArray, H5F_ACC_RDWR, H5P_DEFAULT);
+ delete inputFilenameCharArray;
+ startTime = time(NULL);
+ inputs.readHdf5(fileId);
+ H5Fclose(fileId);
+
+ time_t elapsedTime = time(NULL) - startTime;
+ std::cerr << "load time = " << elapsedTime << " secs" << std::endl;
+}
+
+bool IOHandler::fileExists(const std::string& name) {
+ std::ifstream f(name.c_str());
+ return f.good();
+}
+
+
+void IOHandler::saveOutputs(Persistable& outputs) const {
+
+ time_t startTime;
+ char* outputFilenameCharArray = new char[outputFilename.length()+1];
+ strcpy(outputFilenameCharArray, outputFilename.c_str());
+ H5Pset_libver_bounds(H5P_FILE_ACCESS_DEFAULT, H5F_LIBVER_V18, H5F_LIBVER_V18);
+ hid_t fileId = H5Fcreate(outputFilenameCharArray, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+ delete outputFilenameCharArray;
+ startTime = time(NULL);
+ outputs.writeHdf5(fileId);
+ H5Fclose(fileId);
+
+ time_t elapsedTime = time(NULL) - startTime;
+ std::cerr << "save time = " << elapsedTime << " secs" << std::endl;
+}
+
diff --git a/src/main/cpp/libziggymi/IOHandler.h b/src/main/cpp/libziggymi/IOHandler.h
new file mode 100644
index 0000000..50d5499
--- /dev/null
+++ b/src/main/cpp/libziggymi/IOHandler.h
@@ -0,0 +1,33 @@
+#ifndef IOHANDLER_H_
+#define IOHANDLER_H_
+
+#include
+
+#include
+
+/**
+ * Deserilizes Persistable inputs and serializes Persitable outputs.
+ *
+ * @author Todd Klaus
+ */
+class IOHandler {
+public:
+
+ IOHandler(const std::string& dir, const std::string& id, const std::string& binaryName);
+
+ virtual ~IOHandler() {
+ }
+
+ void loadInputs(Persistable& inputs) const;
+ void saveOutputs(Persistable& outputs) const;
+ static bool fileExists(const std::string& name) ;
+
+private:
+ std::string dir;
+ std::string id;
+ std::string binaryName;
+ std::string inputFilename;
+ std::string outputFilename;
+};
+
+#endif /*IOHANDLER_H_*/
diff --git a/src/main/cpp/libziggymi/Persistable.h b/src/main/cpp/libziggymi/Persistable.h
new file mode 100644
index 0000000..398a5bf
--- /dev/null
+++ b/src/main/cpp/libziggymi/Persistable.h
@@ -0,0 +1,24 @@
+#ifndef PERSISTABLE_H_
+#define PERSISTABLE_H_
+
+#include "hdf5.h"
+
+/**
+ * This is the base class of persistables. Persistables objects that can be serialalized with the
+ * persistable framework.
+ */
+
+class Persistable {
+public:
+
+ Persistable() {
+ };
+
+ virtual ~Persistable() {
+ };
+
+ virtual void readHdf5(hid_t hdf5Id) = 0;
+ virtual void writeHdf5(hid_t hdf5Id) const = 0;
+};
+
+#endif /*PERSISTABLE_H_*/
diff --git a/src/main/java/gov/nasa/ziggy/collections/HyperRectangle.java b/src/main/java/gov/nasa/ziggy/collections/HyperRectangle.java
new file mode 100644
index 0000000..c730955
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/HyperRectangle.java
@@ -0,0 +1,111 @@
+package gov.nasa.ziggy.collections;
+
+import java.util.Arrays;
+
+/**
+ * General representation of a region within a non-ragged, multi-dimensional array.
+ *
+ * @author PT
+ */
+public class HyperRectangle {
+
+ /**
+ * Size of the parent array.
+ */
+ final int[] fullArraySize;
+
+ /**
+ * Size of the hyper-rectangle.
+ */
+ final int[] size;
+
+ /**
+ * Location of the hyper-rectangle within the parent array.
+ */
+ final int[] offset;
+
+ public HyperRectangle(int[] fullArraySize, int[] size, int[] offset) {
+ this.fullArraySize = fullArraySize;
+ this.size = size;
+ this.offset = offset;
+ if (!checkParameters()) {
+ throw new IllegalArgumentException("Unable to obtain hyperslab of size "
+ + Arrays.toString(size) + " and location " + Arrays.toString(offset)
+ + " from array of size " + Arrays.toString(fullArraySize));
+ }
+ }
+
+ /**
+ * Tests that requested hyper-rectangle parameters are valid. Validity means the following: the
+ * requested hyper-rectangle has the same number of parameters as the array; the size and
+ * location requested fit into the array in all dimensions; the requested dimensions consist of
+ * a series of singletons, followed by a dimension that is <= the corresponding size in the
+ * source array, followed by dimensions that are == the corresponding sizes in the source array
+ * (i.e., for an array that is [3][4][5][6], size of [1 1 2 6] is valid but not [1 1 2 1],
+ * because all dimensions past the 3rd must be equal in the array and the hyper-rectangle).
+ *
+ * @return true if all conditions described above hold
+ */
+ private boolean checkParameters() {
+ boolean parametersOkay = true;
+ if (size.length != fullArraySize.length || size.length != offset.length) {
+ return false;
+ }
+
+ boolean pastReducedDimensions = false;
+ for (int iDim = 0; iDim < size.length; iDim++) {
+
+ // super-basic -- dims must all be >= 1, locations must all be >= 0
+ parametersOkay = parametersOkay && size[iDim] >= 1 && fullArraySize[iDim] >= 1
+ && offset[iDim] >= 0;
+
+ if (pastReducedDimensions) {
+ parametersOkay = parametersOkay && size[iDim] == fullArraySize[iDim]
+ && offset[iDim] == 0;
+ } else {
+ parametersOkay = parametersOkay && size[iDim] <= fullArraySize[iDim]
+ && offset[iDim] + size[iDim] <= fullArraySize[iDim] && size[iDim] >= 1;
+ if (size[iDim] > 1) {
+ pastReducedDimensions = true;
+ }
+ }
+ }
+ return parametersOkay;
+
+ }
+
+ /**
+ * Combine the offset array of this HyperRectangle with an additional offset. The method does
+ * not check that the sum of the combined offset + the HyperRectangle length is less than the
+ * full array size, so that the HyperRectangle can be used to read data into a sub-region of an
+ * array that is larger in total than the fullArraySize in the HyperRectangle.
+ *
+ * @param additionalOffset array of additional offset values
+ * @return Sum of additionalOffset and the HyperRectangle's offset
+ */
+ public int[] getOffset(int[] additionalOffset) {
+ if (additionalOffset.length != offset.length) {
+ throw new IllegalArgumentException("Array of length " + additionalOffset.length
+ + " cannot be argument for HyperRectangle with offset array of length "
+ + offset.length);
+ }
+ int[] combinedOffset = new int[offset.length];
+ for (int i = 0; i < combinedOffset.length; i++) {
+ combinedOffset[i] = offset[i] + additionalOffset[i];
+ }
+ return combinedOffset;
+ }
+
+ // getters
+ public int[] getSize() {
+ return size;
+ }
+
+ public int[] getOffset() {
+ return offset;
+ }
+
+ public int[] getFullArraySize() {
+ return fullArraySize;
+ }
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/HyperRectangleIterator.java b/src/main/java/gov/nasa/ziggy/collections/HyperRectangleIterator.java
new file mode 100644
index 0000000..b4c9e26
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/HyperRectangleIterator.java
@@ -0,0 +1,226 @@
+package gov.nasa.ziggy.collections;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator for HyperRectangle. This allows iteration of hyper-rectangular sections of a
+ * multi-dimensional non-ragged array.
+ *
+ * @author PT
+ */
+public class HyperRectangleIterator implements Iterator {
+
+ /**
+ * Size of the standard hyper-rectangle
+ */
+ private final int[] size;
+
+ /**
+ * Size of the last hyper-rectangle -- this can be different from the standard one if the array
+ * size cannot be divided into a whole number of standard hyper-rectangles
+ */
+ private final int[] lastSize;
+
+ /**
+ * Number of hyper-rectangles along each dimension
+ */
+ private final int[] nRectangles;
+
+ /**
+ * Size of the full array that gets subdivided into hyper-rectangles
+ */
+ private final int[] fullArraySize;
+
+ /**
+ * Maximum number of array elements that are permitted per hyper-rectangle
+ */
+ private final int maxElementsPerHyperRectangle;
+
+ /**
+ * Counts the # of slabs per dimension as we proceed through the array
+ */
+ private int[] counter;
+
+ public HyperRectangleIterator(int[] fullArraySize, int maxElementsPerHyperRectangle) {
+ this.fullArraySize = fullArraySize;
+ for (int element : fullArraySize) {
+ if (element <= 0) {
+ throw new IllegalArgumentException(
+ "Arrays with zero-length dimensions are not permitted.");
+ }
+ }
+ this.maxElementsPerHyperRectangle = maxElementsPerHyperRectangle;
+ if (maxElementsPerHyperRectangle <= 0) {
+ throw new IllegalArgumentException(
+ "Number of elements per hyper-rectangle must be positive");
+ }
+ size = size();
+ nRectangles = nRectangles();
+ lastSize = lastSize();
+ counter = new int[lastSize.length];
+ Arrays.fill(counter, 0);
+ }
+
+ /**
+ * Determine the size of the nominal hyper-rectangle for this array, ensuring that (a) the
+ * maximum number of elements per hyper-rectangle is less than or equal to
+ * maxElementsPerHyperRectangle; (b) the hyper-rectangle divides the array along one and only
+ * one dimension; (c) all dimensions lower than the division dimension have 1 "row" per
+ * hyper-rectangle; (d) all dimensions higher than the division dimension have a number of
+ * "columns" per hyper-rectangle equal to that of the parent array. For example, for a 3 x 4 x 5
+ * array, where the max # of elements is 10, will have a standard hyper-rectangle size of 1 x 2
+ * x 5. If the max # of elements is 2, then the standard hyper-rectangle will be 1 x 1 x 2.
+ */
+ private int[] size() {
+ int[] nominalSize = new int[fullArraySize.length];
+
+ // pre-initialize the nominal slab size to be the full array size
+ for (int iDim = 0; iDim < fullArraySize.length; iDim++) {
+ nominalSize[iDim] = fullArraySize[iDim];
+ }
+
+ // loop over dimensions to find the dimension where we need to apply the division into
+ // slabs, if any
+
+ for (int iDim = 0; iDim < fullArraySize.length; iDim++) {
+
+ // determine the number of elements in the array below this level (i.e., the number
+ // of elements per "row" in a generalized sense)
+ long nElemPerRow = 1;
+ for (int jDim = iDim + 1; jDim < fullArraySize.length; jDim++) {
+ nElemPerRow *= fullArraySize[jDim];
+ }
+
+ // determine the number of rows that can be accommodated within the desired max
+ // number of elements
+ long nRows = Math.min(maxElementsPerHyperRectangle / nElemPerRow, fullArraySize[iDim]);
+
+ // if nRows is zero, it means that the # of bytes per row is too large, so we have
+ // to perform the subdivision into hyperslabs at a higher dimension, and this dimension
+ // we should do 1 per hyperslab; alternately, if nRows > 0, we are done with this
+ // activity and can return
+ if (nRows == 0) {
+ nominalSize[iDim] = 1;
+ } else {
+ nominalSize[iDim] = (int) nRows;
+ break;
+ }
+ }
+ return nominalSize;
+ }
+
+ /**
+ * Determine the number of hyper-rectangles in each dimension needed to read/write the array.
+ *
+ * @return number of slabs in each dimension needed to write the array, this is the dimension
+ * size / slab size, plus 1 if there is a remainder.
+ */
+ private int[] nRectangles() {
+ int[] nSlabsPerDim = new int[fullArraySize.length];
+ for (int iDim = 0; iDim < fullArraySize.length; iDim++) {
+
+ // # of slabs is size of the dimension / size of the slab
+ nSlabsPerDim[iDim] = fullArraySize[iDim] / size[iDim];
+
+ // if there's some remainder, then we need 1 more slab
+ if (fullArraySize[iDim] % size[iDim] != 0) {
+ nSlabsPerDim[iDim] += 1;
+ }
+ }
+ return nSlabsPerDim;
+ }
+
+ /**
+ * Determine the size of the last hyper-rectangle in each dimension -- this can be different
+ * from the size of the nominal one if the size of the nominal one does not evenly divide the
+ * array size (for example if array is 3 x 4 x 5 and nominal hyper-rectangle is 1 x 3 x 5, then
+ * the last slab in dim 1 has size 1, the last in dim 2 has size 1, and the last in dim 3 has
+ * size 5)
+ *
+ * @return size of the final slab in each dimension
+ */
+ int[] lastSize() {
+ int[] lastSlabSize = new int[fullArraySize.length];
+ for (int iDim = 0; iDim < fullArraySize.length; iDim++) {
+ int remainder = fullArraySize[iDim] % size[iDim];
+ lastSlabSize[iDim] = remainder == 0 ? size[iDim] : remainder;
+ }
+ return lastSlabSize;
+ }
+
+ @Override
+ public boolean hasNext() {
+
+ // when every hyper-rectangle has been processed, the first entry in nRectangles
+ // will be equal to the # of hyper-rectangles in that direction, thanks to
+ // zero-based indexing
+ return counter[0] < nRectangles[0];
+ }
+
+ @Override
+ public HyperRectangle next() {
+
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ // construct arrays for use in instantiating a HyperRectangle:
+ int[] slabSize = new int[counter.length];
+ int[] location = new int[counter.length];
+
+ // populate the arrays
+ for (int iDim = 0; iDim < counter.length; iDim++) {
+ location[iDim] = counter[iDim] * size[iDim];
+ if (counter[iDim] == nRectangles[iDim] - 1) {// last slab in this direction
+ slabSize[iDim] = lastSize[iDim];
+ } else {
+ slabSize[iDim] = size[iDim];
+ }
+ }
+
+ // construct the HyperRectangle object
+ HyperRectangle slab = new HyperRectangle(fullArraySize, slabSize, location);
+
+ // update the counters -- update the fastest-moving index until we run out
+ // of values, at that point set it to zero and update the next-fastest-moving; when
+ // the zeroth index is up to its max value, stop updating (this will signal hasNext()
+ // to return false)
+ for (int iDim = counter.length - 1; iDim >= 0; iDim--) {
+ counter[iDim]++;
+ if (counter[iDim] == nRectangles[iDim] && iDim > 0) {
+ counter[iDim] = 0;
+ } else {
+ break;
+ }
+ }
+ return slab;
+ }
+
+ // getters
+ public int[] getSize() {
+ return size;
+ }
+
+ public int[] getLastSize() {
+ return lastSize;
+ }
+
+ public int[] getnRectangles() {
+ return nRectangles;
+ }
+
+ public int[] getFullArraySize() {
+ return fullArraySize;
+ }
+
+ public int getMaxElementsPerHyperRectangle() {
+ return maxElementsPerHyperRectangle;
+ }
+
+ public int[] getCounter() {
+ return counter;
+ }
+
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/ListChunkIterator.java b/src/main/java/gov/nasa/ziggy/collections/ListChunkIterator.java
new file mode 100644
index 0000000..3cbbb5c
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/ListChunkIterator.java
@@ -0,0 +1,52 @@
+package gov.nasa.ziggy.collections;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Given a list, returns successive sub-lists which encompass all of the elements of the list. Each
+ * sub-list is limited to some maximum size.
+ *
+ * @author Sean McCauliff
+ */
+public class ListChunkIterator implements Iterator>, Iterable> {
+ private final Iterator source;
+ private final int chunkSize;
+
+ public ListChunkIterator(Iterator source, int chunkSize) {
+ if (source == null) {
+ throw new NullPointerException("source");
+ }
+ this.source = source;
+ this.chunkSize = chunkSize;
+ }
+
+ public ListChunkIterator(Iterable source, int chunkSize) {
+ this(source.iterator(), chunkSize);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return source.hasNext();
+ }
+
+ @Override
+ public List next() {
+ List rv = new ArrayList<>(chunkSize);
+ for (int i = 0; i < chunkSize && source.hasNext(); i++) {
+ rv.add(source.next());
+ }
+ return rv;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Operation not supported.");
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return this;
+ }
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/RemovableArrayList.java b/src/main/java/gov/nasa/ziggy/collections/RemovableArrayList.java
new file mode 100644
index 0000000..e14b94f
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/RemovableArrayList.java
@@ -0,0 +1,120 @@
+package gov.nasa.ziggy.collections;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Allows for slightly more efficent means of removing and insterting intervals of data from an
+ * ArrayList.
+ *
+ * Exposes the method which removes a range of elements from the ArrayList which for some reason is
+ * protected in java.util.ArrayList.
+ *
+ * @author Sean McCauliff
+ */
+public class RemovableArrayList extends ArrayList {
+ private static final long serialVersionUID = -7536249496170417383L;
+
+ public RemovableArrayList() {
+ }
+
+ /**
+ * @param initialCapacity
+ */
+ public RemovableArrayList(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * @param c
+ */
+ public RemovableArrayList(Collection c) {
+ super(c);
+ }
+
+ /**
+ * Removes a section of the array. ArrayList implements this method as removeRange, but does not
+ * expose it as public.
+ *
+ * @param start
+ * @param end Exclusive
+ */
+ public void removeInterval(int start, int end) {
+ super.removeRange(start, end);
+ }
+
+ /**
+ * Inserts data at the speciried location.
+ */
+ public void insertAt(int start, final T[] data) {
+ Collection tmpCollection = new Collection() {
+ @Override
+ public boolean add(T e) {
+ throw new IllegalStateException("Read only.");
+ }
+
+ @Override
+ public boolean addAll(Collection extends T> c) {
+ throw new IllegalStateException("Read only.");
+ }
+
+ @Override
+ public void clear() {
+ throw new IllegalStateException("Read only.");
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ throw new IllegalStateException("Not implemented.");
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ throw new IllegalStateException("Not implemented.");
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return data.length == 0;
+ }
+
+ @Override
+ public Iterator iterator() {
+ throw new IllegalStateException("Not implemented.");
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new IllegalStateException("Read only.");
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ throw new IllegalStateException("Read only.");
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ throw new IllegalStateException("Not implemented.");
+ }
+
+ @Override
+ public int size() {
+ return data.length;
+ }
+
+ @Override
+ public Object[] toArray() {
+ return data;
+ }
+
+ @Override
+ public TRv[] toArray(TRv[] a) {
+ throw new IllegalStateException("Not implemented.");
+ }
+ };
+
+ super.addAll(start, tmpCollection);
+ }
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/ZiggyArrayUtils.java b/src/main/java/gov/nasa/ziggy/collections/ZiggyArrayUtils.java
new file mode 100644
index 0000000..bfd739a
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/ZiggyArrayUtils.java
@@ -0,0 +1,753 @@
+package gov.nasa.ziggy.collections;
+
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_BOOLEAN;
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_DOUBLE;
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_ENUM;
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_INT;
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_LONG;
+import static gov.nasa.ziggy.collections.ZiggyDataType.ZIGGY_STRING;
+import static gov.nasa.ziggy.collections.ZiggyDataType.box1dArray;
+import static gov.nasa.ziggy.collections.ZiggyDataType.castBoxedNumericToUnboxedNumeric;
+import static gov.nasa.ziggy.collections.ZiggyDataType.fill1dArray;
+import static gov.nasa.ziggy.collections.ZiggyDataType.getDataType;
+import static gov.nasa.ziggy.collections.ZiggyDataType.getDataTypeFromClass;
+import static gov.nasa.ziggy.collections.ZiggyDataType.truncateClassName;
+import static gov.nasa.ziggy.collections.ZiggyDataType.unbox1dArray;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.primitives.Longs;
+
+import gov.nasa.ziggy.module.PipelineException;
+
+/**
+ * Utility methods for single- and multi-dimensional arrays, with a few utilities for primitive
+ * scalars thrown in. For the time being these methods are focused on the numeric classes (byte,
+ * short, int, long, float, double) plus boolean and String. Support for arrays of objects may
+ * happen if a need presents itself.
+ *
+ * @author PT
+ */
+public class ZiggyArrayUtils {
+
+ private ZiggyArrayUtils() {
+
+ }
+
+ /**
+ * Shortcut method to cast a 1-d long array to 1-d int
+ *
+ * @param long1d 1-d array of longs
+ * @return long1d, converted to 1-d array of ints
+ */
+ public static int[] longToInt1d(long[] long1d) {
+ return (int[]) castArray(long1d, ZIGGY_INT);
+ }
+
+ /**
+ * Shortcut method to cast a 1-d int array to 1-d long
+ *
+ * @param int1d 1-d array of ints
+ * @return int1d, converted to 1-d array of longs
+ */
+ public static long[] intToLong1d(int[] int1d) {
+ return (long[]) castArray(int1d, ZIGGY_LONG);
+ }
+
+ /**
+ * Detects fields of boxed primitive values.
+ *
+ * @param field field of a class that might be for boxed primitive values. This field can be
+ * scalar or list as well as array.
+ * @return
+ */
+ public static boolean isBoxedPrimitive(Field field) {
+ String className = null;
+ if (field.getType().isArray()) {
+ return isBoxedPrimitive(field.getType());
+ } else if (field.getType().getName().equals("java.util.List")) {
+ Type genericType = field.getGenericType();
+ ParameterizedType pt = (ParameterizedType) genericType;
+ className = pt.getActualTypeArguments()[0].getTypeName();
+ } else {
+ className = field.getType().getName();
+ }
+ if (className.contains("Boolean") || className.contains("Byte")
+ || className.contains("Short") || className.contains("Integer")
+ || className.contains("Long") || className.contains("Float")
+ || className.contains("Double")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Detects arrays of boxed primitive values.
+ *
+ * @param arrayObject Object to be examined. Must be an array, not a scalar or list.
+ * @return
+ */
+ public static boolean isBoxedPrimitive(Object arrayObject) {
+ return isBoxedPrimitive(arrayObject.getClass());
+ }
+
+ public static boolean isBoxedPrimitive(Class> clazz) {
+ Class> testClass = null;
+ String className = truncateClassName(clazz.getName());
+ if (className.length() == 1) { // primitive array
+ return false;
+ }
+ try {
+ testClass = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new PipelineException("Unable to determine class with name: " + className, e);
+ }
+ Class> numberClass = Number.class;
+ return testClass.equals(numberClass) || testClass.equals(Boolean.class)
+ || numberClass.isAssignableFrom(testClass);
+ }
+
+ /**
+ * Determine the size of an array. The number of dimensions of the array are initially unknown.
+ *
+ * @param dataObject The array that is to be sized.
+ * @return long[] with the size of each dimension in the array.
+ */
+ public static long[] getArraySize(Object dataObject) {
+ List arrayDimensionList = new ArrayList<>();
+ Class extends Object> currentClass = dataObject.getClass();
+ Object loopObject = dataObject;
+ boolean isArray = currentClass.isArray();
+ if (!isArray) {
+ return new long[] { 0L };
+ }
+
+ // This needs to be done in this somewhat squirrely fashion because on the last
+ // iteration, if loopObject is a 1-d array of primitives, it will still have
+ // isArray() == true, but an attempt to cast it from Object to Object[] will fail
+ while (isArray) {
+ arrayDimensionList.add((long) Array.getLength(loopObject));
+ String componentType = loopObject.getClass().getComponentType().getName();
+ if (componentType.startsWith("[")) {
+ Object[] arrayObject = (Object[]) loopObject;
+ loopObject = arrayObject[0];
+ isArray = true;
+ } else {
+ isArray = false;
+ }
+
+ }
+ long[] arrayDimensions = new long[arrayDimensionList.size()];
+ arrayDimensions = Longs.toArray(arrayDimensionList);
+ return arrayDimensions;
+
+ }
+
+ /**
+ * Unbox a multi-dimensinonal array of boxed values.
+ *
+ * @param boxedObject Array of boxed values. Array must be a hyper-rectangle (i.e., not ragged),
+ * but otherwise any size or shape is acceptable.
+ * @return Array containing the values from boxedObject, converted to their primitive
+ * equivalents. Size and shape will match those of boxedObject.
+ */
+ public static Object unbox(Object boxedObject) {
+ long[] dimensions = getArraySize(boxedObject);
+ Object destinationArray = constructPrimitiveArray(dimensions.length, (int) dimensions[0],
+ getDataType(boxedObject));
+ Object[] destinationArray1 = dimensions.length > 1 ? (Object[]) destinationArray : null;
+ ArrayRecursionLevel nextLevel = (obj, i) -> destinationArray1[i] = unbox(obj);
+ ArrayRecursionLevel lowestLevel = (arrayObject, i) -> {
+ Object unboxedArray = unbox1dArray(arrayObject);
+ System.arraycopy(unboxedArray, 0, destinationArray, 0, (int) dimensions[0]);
+ };
+ arrayLevelRecursionMaster(boxedObject, nextLevel, lowestLevel);
+ return destinationArray;
+ }
+
+ /**
+ * Convert a multi-dimensional array of primitive values to the equivalent array of boxed values
+ *
+ * @param unboxedObject Array of primitive values. Array must be a hyper-rectangle (i.e., not
+ * ragged), but otherwise any size or shape will be accepted.
+ * @return contents of unboxedObject in an array of boxed values. The boxed type will be the
+ * appropriate type for the type of primitive in unboxedObject. The size and shape of the
+ * returned array will match those of unboxedObject.
+ */
+ public static Object box(Object unboxedObject) {
+ long[] dimensions = getArraySize(unboxedObject);
+ Object destinationArray = constructBoxedArray(dimensions.length, (int) dimensions[0],
+ getDataType(unboxedObject));
+ Object[] destinationArray1 = dimensions.length > 1 ? (Object[]) destinationArray : null;
+ ArrayRecursionLevel nextLevel = (obj, i) -> destinationArray1[i] = box(obj);
+ ArrayRecursionLevel lowestLevel = (arrayObject, i) -> {
+ Object boxedArray = box1dArray(arrayObject);
+ System.arraycopy(boxedArray, 0, destinationArray, 0, (int) dimensions[0]);
+ };
+ arrayLevelRecursionMaster(unboxedObject, nextLevel, lowestLevel);
+ return destinationArray;
+ }
+
+ /**
+ * Cast a multi-dimensional array of primitives to a different type. Permitted casts are: from
+ * any numeric class to any other numeric class; numeric to boolean or boolean to numeric;
+ * String to Enum or Enum to String.
+ *
+ * @param sourceArray
+ * @param castType type desired for cast. This can be a ZiggyDataType for the typical primitive
+ * types plus string, or it can be the Class object of an Enum class (i.e., MyEnum.Class) for
+ * casts to Enum.
+ * @return cast of SourceArray to desired type
+ */
+ public static Object castArray(Object sourceArray, Object castType) {
+ ZiggyDataType hType = castDataType(castType);
+ long[] arraySize = getArraySize(sourceArray);
+ Object destinationArray = constructPrimitiveArray(arraySize.length, (int) arraySize[0],
+ hType);
+ Object[] destArray1 = arraySize.length > 1 ? (Object[]) destinationArray : null;
+ ArrayRecursionLevel nextLevel = (obj, i) -> destArray1[i] = castArray(obj, castType);
+ ArrayRecursionLevel lowestLevel = new ArrayRecursionLevel() {
+ // TODO Try to eliminate this SuppressWarnings annotation
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void apply(Object sourceArray1, int i) {
+ ZiggyDataType hType = castDataType(castType);
+ Object destinationArray1 = null;
+ ZiggyDataType sourceDataType = getDataType(sourceArray1);
+
+ // there are a few options here:
+ if (sourceDataType.isNumeric() && hType.equals(ZIGGY_BOOLEAN)) {
+ destinationArray1 = castNumericToBoolean(sourceArray1);
+ }
+ if (sourceDataType.equals(ZIGGY_BOOLEAN) && hType.isNumeric()) {
+ destinationArray1 = castBooleanToNumeric(sourceArray1, hType);
+ }
+ if (sourceDataType.equals(ZIGGY_ENUM) && hType.equals(ZIGGY_STRING)) {
+ destinationArray1 = castEnumToString(sourceArray1);
+ }
+ if (sourceDataType.equals(ZIGGY_STRING) && hType.equals(ZIGGY_ENUM)) {
+ destinationArray1 = castStringToEnum(sourceArray1, (Class) castType);
+ }
+ if (sourceDataType.isNumeric() && hType.isNumeric()) {
+ destinationArray1 = castNumericToNumeric(sourceArray1, hType);
+ }
+ if (destinationArray1 == null) {
+ throw new PipelineException("Unable to cast array from "
+ + sourceDataType.getJavaClassName() + " to " + hType.getJavaClassName());
+ }
+ System.arraycopy(destinationArray1, 0, destinationArray, 0, (int) arraySize[0]);
+ }
+ };
+ arrayLevelRecursionMaster(sourceArray, nextLevel, lowestLevel);
+ return destinationArray;
+ }
+
+ /**
+ * Utility method that converts an Enum.class object to an Enum DataType object, but otherwise
+ * assumes that its argument is a DataType and returns it.
+ */
+ static ZiggyDataType castDataType(Object castType) {
+ if (isEnumClass(castType)) {
+ return ZiggyDataType.ZIGGY_ENUM;
+ } else {
+ return (ZiggyDataType) castType;
+ }
+ }
+
+ /**
+ * Cast a numeric 1-d array to boolean.
+ *
+ * @param sourceArray1d 1-d array of boolean
+ * @return contents of sourceArray1, cast to boolean, where zero values are mapped to false and
+ * nonzero to true.
+ */
+ static Object castNumericToBoolean(Object sourceArray1d) {
+
+ // perform the ultimate in widening conversions
+ Object doubleObject = castNumericToNumeric(sourceArray1d, ZIGGY_DOUBLE);
+ double[] doubleArray = (double[]) doubleObject;
+ boolean[] booleanArray = new boolean[doubleArray.length];
+ for (int i = 0; i < doubleArray.length; i++) {
+ booleanArray[i] = doubleArray[i] != 0;
+ }
+ return booleanArray;
+ }
+
+ /**
+ * Cast a 1-d array of boolean values to a numeric class.
+ *
+ * @param sourceArray1 1-d array of boolean values
+ * @param ziggyDataType ZiggyDataType of desired cast outcome
+ * @return contents of sourceArray1d, cast to the type indicated by hType, where false values
+ * are mapped to 0 and true values are mapped to 1.
+ */
+ static Object castBooleanToNumeric(Object sourceArray1d, ZiggyDataType ziggyDataType) {
+ boolean[] booleanArray = (boolean[]) sourceArray1d;
+ byte[] byteArray = new byte[booleanArray.length];
+ for (int i = 0; i < booleanArray.length; i++) {
+ byteArray[i] = booleanArray[i] ? (byte) 1 : (byte) 0;
+ }
+ return castNumericToNumeric(byteArray, ziggyDataType);
+ }
+
+ /**
+ * Cast a 1-d array of Enums to String.
+ *
+ * @param sourceArrayCurrentLevel 1-d array of Enums
+ * @return contents of sourceArrayCurrentLevel, cast to Strings
+ */
+ @SuppressWarnings("unchecked")
+ static Object castEnumToString(Object sourceArrayCurrentLevel) {
+ E[] sourceArray = (E[]) sourceArrayCurrentLevel;
+ String[] destinationArray = new String[sourceArray.length];
+ for (int i = 0; i < sourceArray.length; i++) {
+ destinationArray[i] = ((Enum>) sourceArray[i]).name();
+ }
+ return destinationArray;
+ }
+
+ /**
+ * Cast a 1-d array of strings to a specified Enum class.
+ *
+ * @param sourceArrayCurrentLevel 1-d array of strings
+ * @param enumClass Class of desired Enum storage type
+ * @return contents of sourceArrayCurrentLevel, cast to Enum type indicated by enumClass
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ static > Object castStringToEnum(Object sourceArrayCurrentLevel,
+ Class extends Enum> enumClass) {
+ String[] stringArray = (String[]) sourceArrayCurrentLevel;
+ Class extends Enum> E = enumClass;
+ Enum[] genericEnumArray = new Enum[stringArray.length];
+ E[] enumArray = (E[]) genericEnumArray;
+ for (int i = 0; i < stringArray.length; i++) {
+ enumArray[i] = (E) Enum.valueOf(E, stringArray[i]);
+ }
+ return enumArray;
+ }
+
+ /**
+ * Cast a numeric 1-d array to a numeric array of a different data type.
+ *
+ * @param sourceArray1d 1-d numeric array
+ * @param ziggyDataType data type of desired cast
+ * @return contents of sourceArray1d, cast to type indicated by ziggyDataType
+ */
+ static Object castNumericToNumeric(Object sourceArray1d, ZiggyDataType ziggyDataType) {
+ if (ziggyDataType.equals(getDataType(sourceArray1d))) {
+ return sourceArray1d;
+ }
+ Number[] boxedSourceArray = (Number[]) box(sourceArray1d);
+ Object destinationArray = Array.newInstance(ziggyDataType.getJavaClass(),
+ boxedSourceArray.length);
+ castBoxedNumericToUnboxedNumeric(boxedSourceArray, destinationArray);
+ return destinationArray;
+ }
+
+ /**
+ * Construct a complete multi-dimensional array of a specified primitive type.
+ *
+ * @param dimensions array with the dimensions of the desired array.
+ * @param ziggyDataType data type for the desired array type.
+ * @return Constructed, fully-populated array. For example, a call with dimensions == {3, 4, 5}
+ * and ZiggyDataType == ZIGGY_FLOAT will return an array of float[3][4][5].
+ */
+ public static Object constructFullPrimitiveArray(long[] dimensions,
+ ZiggyDataType ziggyDataType) {
+ return constructFullArray(dimensions, ziggyDataType, false);
+ }
+
+ /**
+ * Construct a complete multi-dimensional array of a specified boxed type.
+ *
+ * @param dimensions array with the dimensions of the desired array.
+ * @param ziggyDataType data type for the desired array type.
+ * @return Constructed, fully-populated array. For example, a call with dimensions == {3, 4, 5}
+ * and ZiggyDataType == ZIGGY_FLOAT will return an array of Float[3][4][5].
+ */
+ public static Object constructFullBoxedArray(long[] dimensions, ZiggyDataType ziggyDataType) {
+ return constructFullArray(dimensions, ziggyDataType, true);
+ }
+
+ /**
+ * Master builder for multi-dimensional full arrays
+ *
+ * @param dimensions dimensions of the array
+ * @param ziggyDataType data type for the desired storage class.
+ * @param box if true return array of boxed values, otherwise primitives.
+ * @return Array of the desired dimensions and storage class.
+ */
+ public static Object constructFullArray(long[] dimensions, ZiggyDataType ziggyDataType,
+ boolean box) {
+
+ // build the outermost array (so for example float[3][][])
+ Object returnObject = constructArray(dimensions.length, (int) dimensions[0], ziggyDataType,
+ box);
+
+ // If the array is not 1-d, populate the next level down (i.e.,
+ // make each of the array[i] equal to float[4][]). This uses a
+ // recursive call to this method.
+ if (dimensions.length > 1) {
+ Object[] returnArray = (Object[]) returnObject;
+ for (int i = 0; i < returnArray.length; i++) {
+ returnArray[i] = constructFullArray(
+ Arrays.copyOfRange(dimensions, 1, dimensions.length), ziggyDataType, box);
+ }
+ }
+ return returnObject;
+ }
+
+ /**
+ * Construct an empty array for a primitive type at some level of dimensionality.
+ *
+ * @param dimensionCount Number of dimensions (i.e., number of [] at the end)
+ * @param length array length
+ * @param ziggyDataType data type to be used for the array
+ * @return An array with the dimensionality and primitive type requested, i.e., dimensionCount
+ * == 3, length = 4, data type == ZIGGY_DOUBLE, will return an array of double[4][][]. Note that
+ * the lower-level arrays are null. To achieve an array that is fully populated, use
+ * constructFullPrimitiveArray.
+ */
+ public static Object constructPrimitiveArray(int dimensionCount, int length,
+ ZiggyDataType ziggyDataType) {
+ return constructArray(dimensionCount, length, ziggyDataType, false);
+ }
+
+ /**
+ * Construct an empty array for a boxed type at some level of dimensionality.
+ *
+ * @param dimensionCount Number of dimensions (i.e., number of [] at the end)
+ * @param length array length
+ * @param ziggyDataType data type to be used for the array
+ * @return An array with the dimensionality and primitive type requested, i.e., dimensionCount
+ * == 3, length = 4, data type == ZIGGY_DOUBLE, will return an array of Double[4][][]. Note that
+ * the lower-level arrays are null. To achieve an array that is fully populated, use
+ * constructFullBoxedArray.
+ */
+ public static Object constructBoxedArray(int dimensionCount, int length,
+ ZiggyDataType ziggyDataType) {
+ return constructArray(dimensionCount, length, ziggyDataType, true);
+ }
+
+ /**
+ * Master array constructor for numeric, string, and enum types.
+ *
+ * @param dimensionCount Number of dimensions for the array.
+ * @param length Length of the highest level of the array (i.e., number of "rows")
+ * @param ziggyDataType DataType for the array
+ * @param box if true, return an array of boxed types, otherwise primitives
+ * @return Array of the desired type and size.
+ */
+ static Object constructArray(int dimensionCount, int length, ZiggyDataType ziggyDataType,
+ boolean box) {
+ String arrayClass = arrayClassToConstruct(dimensionCount, ziggyDataType, box);
+ try {
+
+ if (arrayClass.length() > 1) {
+
+ // in this case, we have the correct name for the class
+ return Array.newInstance(Class.forName(arrayClass), length);
+ } else {
+
+ // in this case we want a 1-d array of primitives, so we
+ // cannot use the class name...
+ return Array.newInstance(ziggyDataType.getJavaClass(), length);
+ }
+ } catch (NegativeArraySizeException | ClassNotFoundException e) {
+ throw new PipelineException("Unable to construct new array for class " + arrayClass, e);
+ }
+ }
+
+ /**
+ * Determines the name of the array class for construction.
+ *
+ * @param dimensionCount Number of dimensions desired.
+ * @param ziggyDataType data type for the desired array storage class.
+ * @param box if true return array of boxed values, otherwise primitives.
+ * @return String containing the name of the array class to be constructed.
+ */
+ static String arrayClassToConstruct(int dimensionCount, ZiggyDataType ziggyDataType,
+ boolean box) {
+ StringBuilder arrayStringBuilder = new StringBuilder();
+ for (int i = 1; i < dimensionCount; i++) {
+ arrayStringBuilder.append("[");
+ }
+ switch (ziggyDataType) {
+ case ZIGGY_STRING:
+ arrayStringBuilder.append("java.lang.String");
+ if (dimensionCount > 1) {
+ arrayStringBuilder.insert(dimensionCount - 1, "L");
+ arrayStringBuilder.append(";");
+ }
+ break;
+ case ZIGGY_ENUM:
+ arrayStringBuilder.append("java.lang.Enum");
+ if (dimensionCount > 1) {
+ arrayStringBuilder.insert(dimensionCount - 1, "L");
+ arrayStringBuilder.append(";");
+ }
+ break;
+ default:
+ if (box) {
+ arrayStringBuilder.append(ziggyDataType.getJavaBoxedClassName());
+ if (dimensionCount > 1) {
+ arrayStringBuilder.insert(dimensionCount - 1, "L");
+ arrayStringBuilder.append(";");
+ }
+ } else {
+ arrayStringBuilder.append(ziggyDataType.getJavaTypeCharacter());
+ }
+ }
+ String arrayClass = arrayStringBuilder.toString();
+ return arrayClass;
+ }
+
+ @SuppressWarnings("rawtypes")
+ static boolean isEnumClass(Object obj) {
+ if (obj instanceof Class) {
+ return getDataTypeFromClass((Class) obj).equals(ZIGGY_ENUM);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Constructs a multi-dimensional boolean array and populates it with trues
+ *
+ * @param size desired size of the array
+ * @return boolean array of desired size, all values set to true
+ */
+ public static Object arrayOfTrue(long[] size) {
+ Object array = constructFullPrimitiveArray(size, ZIGGY_BOOLEAN);
+ populateWithTrue(array);
+ return array;
+ }
+
+ /**
+ * Constructs a multi-dimensional boolean array and populates it with trues
+ *
+ * @param size desired size of the array
+ * @return boolean array of desired size, all values set to true
+ */
+ public static Object arrayOfTrue(int[] size) {
+ return arrayOfTrue(intToLong1d(size));
+ }
+
+ /**
+ * Recursively populates a multi-dimensional boolean array with true values.
+ *
+ * @param array Array to be populated.
+ */
+ static void populateWithTrue(Object array) {
+ ArrayRecursionLevel nextLevel = (obj, i) -> populateWithTrue(obj);
+ ArrayRecursionLevel lowestLevel = (array1, c) -> {
+ boolean[] bools = (boolean[]) array1;
+ for (int i = 0; i < bools.length; i++) {
+ bools[i] = true;
+ }
+ };
+ arrayLevelRecursionMaster(array, nextLevel, lowestLevel);
+ }
+
+ /**
+ * Fill a multi-dimensional array with a numeric fill value. Note that the array must be a
+ * primitive array of numeric type (byte, short, int, long, float, double).
+ *
+ * @param array Array to be filled
+ * @param fillValue numeric value to be used in the fill
+ */
+ public static void fill(Object array, Number fillValue) {
+ fillRecursively(array, fillValue);
+ }
+
+ /**
+ * Fill a multi-dimensional array with a Java Object fill value. Note that the array must be
+ * either a prmitive array of numeric type, a String array, or a boolean array.
+ *
+ * @param array Array to be filled
+ * @param fillValue Object value to be used in the fill. If the array is numeric, fillValue must
+ * be a Number; if boolean, it must be a Boolean; if the array is Strings, fillValue must
+ * implement the toString() method appropriately.
+ */
+ public static void fill(Object array, Object fillValue) {
+ fillRecursively(array, fillValue);
+ }
+
+ /**
+ * Recursive array filler.
+ *
+ * @param array Array to be filled.
+ * @param fillValue Object that supplies the fill value.
+ */
+ static void fillRecursively(Object array, Object fillValue) {
+ ArrayRecursionLevel nextLevel = (array1, i) -> fillRecursively(array1, fillValue);
+ ArrayRecursionLevel lowestLevel = (array1, i) -> {
+ fill1dArray(array1, fillValue);
+ };
+ arrayLevelRecursionMaster(array, nextLevel, lowestLevel);
+ }
+
+ /**
+ * Determines whether an array is "ragged," that is to say, whether the array is a proper
+ * hyper-rectangle (not ragged) or has sub-arrays that differ in size (ragged).
+ *
+ * @param array Array to be analyzed
+ * @return false if array is a hyper-rectangle, true if it is ragged.
+ */
+ public static boolean isRaggedArray(Object array) {
+
+ // the ragged return value needs to be both accessed and mutated by
+ // the nextLevel object, but it also has to be effectively final; the
+ // only way I've found to do this is to make it a boolean[1] array
+ boolean[] ragged = new boolean[] { false };
+ long[] fullArraySize = getArraySize(array);
+ int nDims = fullArraySize.length;
+ long[] subarraySize = ArrayUtils.subarray(fullArraySize, 1, nDims);
+ ArrayRecursionLevel nextLevel = (subArray, i) -> {
+ ragged[0] = ragged[0] || !Arrays.equals(subarraySize, getArraySize(subArray));
+ ragged[0] = ragged[0] || isRaggedArray(subArray);
+ };
+ ArrayRecursionLevel lowestLevel = (lowestArray, i) -> {
+ };
+ arrayLevelRecursionMaster(array, nextLevel, lowestLevel);
+ return ragged[0];
+ }
+
+ /**
+ * Gets a selected value out of a multi-dimensional array. The array can be any boxed or unboxed
+ * numeric type, boxed or unboxed boolean, or String.
+ *
+ * @param array
+ * @param location int array that holds the desired location: for example, to retrieve
+ * array[3][2][4], location should be {3, 2, 4}.
+ * @return The value from the selected location, wrapped if the array is of primitive values.
+ */
+ public static Object getValue(Object array, int[] location) {
+ Object obj = null;
+ if (location.length > 1) {
+ Object[] objArray = (Object[]) array;
+ obj = getValue(objArray[location[0]],
+ ArrayUtils.subarray(location, 1, location.length));
+ } else {
+ obj = Array.get(array, location[0]);
+
+ }
+ return obj;
+ }
+
+ public static void setValue(Object array, int[] location, Object value) {
+ long[] arraySize = getArraySize(array);
+ if (arraySize.length > 1) {
+ Object[] objArray = (Object[]) array;
+ setValue(objArray[location[0]], ArrayUtils.subarray(location, 1, location.length),
+ value);
+ } else {
+ Array.set(array, location[0], value);
+ }
+ }
+
+ /**
+ * Recurses through array levels. If the recursion has not yet reached the lowest level, execute
+ * a next-level method. At the lowest level, execute a lowest-level method. The next-level and
+ * lowest-level methods are supplied via the ArrayRecursionLevel interface, so methods can call
+ * arrayLevelRecursionMaster() with a pair of anonymous instances of ArrayRecursionLevel.
+ *
+ * @param array array to be operated on, as an Object
+ * @param nextLevel ArrayRecursionLevel instance to be used for next-lowest level processing;
+ * usually this is a call to the method that calls arrayLevelRecursionMaster(), but additional
+ * processing may also be needed
+ * @param lowestLevel ArrayRecursionLevel instance to be used for lowest-level processing
+ */
+ static void arrayLevelRecursionMaster(Object array, ArrayRecursionLevel nextLevel,
+ ArrayRecursionLevel lowestLevel) {
+ long[] arraySize = getArraySize(array);
+ if (arraySize.length > 1) {
+ Object[] objectArray = (Object[]) array;
+ for (int i = 0; i < objectArray.length; i++) {
+ nextLevel.apply(objectArray[i], i);
+ }
+ } else {
+ lowestLevel.apply(array, 0);
+ }
+ }
+
+ /**
+ * Interface that supports recursion through array levels. The interface requires a single
+ * method, go(), that takes as argument a Java Object and an int. This permits anonymous classes
+ * based on ArrayRecursionLevel to perform operations on Java arrays or array segments that are
+ * passed as Objects.
+ *
+ * @author PT
+ */
+ interface ArrayRecursionLevel {
+ void apply(Object obj, int counter);
+ }
+
+ /**
+ * This can copy any kind of array even primitive arrays, hence this is not parameterized.
+ *
+ * @param src The source array.
+ * @param componentType The element type of the destination array. This may be different from
+ * the src component type if the element of the src array are assignable to the componentType.
+ * @return An array object which has the specified component type.
+ */
+ public static Object copyArray(Object src, Class> componentType) {
+ // Class> originalArrayComponentType =
+ // originalArray.getClass().getComponentType();
+ int originalArrayLength = Array.getLength(src);
+
+ Object newArray = Array.newInstance(componentType, originalArrayLength);
+
+ for (int i = 0; i < originalArrayLength; i++) {
+ Array.set(newArray, i, Array.get(src, i));
+ }
+
+ return newArray;
+ }
+
+ /**
+ * Converts a 1-d array to a comma-separated string of values.
+ */
+ public static String arrayToString(Object arrayObject) {
+ ZiggyDataType dataType = ZiggyDataType.getDataType(arrayObject);
+ long[] arraySize = ZiggyArrayUtils.getArraySize(arrayObject);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < (int) arraySize[0]; i++) {
+ sb.append(dataType.array1dMemberToString(arrayObject, i));
+ if (i < arraySize[0] - 1) {
+ sb.append(",");
+ }
+ }
+ return sb.toString();
+ }
+
+ public static Object stringToArray(String commaSeparatedValues, ZiggyDataType dataType) {
+
+ if (StringUtils.isEmpty(commaSeparatedValues)) {
+ return constructFullPrimitiveArray(new long[] { 0 }, dataType);
+ }
+
+ // Convert the string into an array of strings by breaking at commas.
+ String[] stringValues = commaSeparatedValues.split(",");
+
+ // Construct the array.
+ Object array = constructFullPrimitiveArray(new long[] { stringValues.length }, dataType);
+
+ // Populate the array.
+ for (int arrayIndex = 0; arrayIndex < stringValues.length; arrayIndex++) {
+ setValue(array, new int[] { arrayIndex },
+ dataType.typedValue(stringValues[arrayIndex].trim()));
+ }
+ return array;
+ }
+
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/ZiggyDataType.java b/src/main/java/gov/nasa/ziggy/collections/ZiggyDataType.java
new file mode 100644
index 0000000..0181d23
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/ZiggyDataType.java
@@ -0,0 +1,1074 @@
+package gov.nasa.ziggy.collections;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.primitives.Primitives;
+
+import gov.nasa.ziggy.module.PipelineException;
+import gov.nasa.ziggy.module.io.Persistable;
+import hdf.hdf5lib.HDF5Constants;
+
+/**
+ * Provides information about array data types used in Ziggy, including relationship to HDF5 data
+ * types. Objects of the {@link ZiggyDataType} class can also be used to record the data type of
+ * objects, construct arrays in the {@link ZiggyArrayUtils} class, etc.
+ *
+ * @see
+ * List of Java field type characters.
+ * @author PT
+ */
+
+public enum ZiggyDataType {
+
+ ZIGGY_BOOLEAN(boolean.class, "Z", false, ZiggyDataTypeConstants.BOOLEAN_TYPE_INT,
+ ZiggyDataTypeConstants.BOOLEAN_DUMMY_TYPE, "H5T_NATIVE_INT8") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Boolean(((boolean[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((boolean[]) array1d)[location] = (Boolean) arrayMember;
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((boolean[]) array1d, (Boolean) fillValue);
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Boolean(false);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Boolean[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((boolean[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] source, Object destinationArray) {
+ throw new UnsupportedOperationException("Cannot cast numeric array to boolean array");
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Boolean.toString(((boolean[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Boolean) obj).toString() : "false";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ Object typedValue = StringUtils.isEmpty(value) ? false : new Boolean(value);
+ return typedValue;
+ }
+
+ },
+ ZIGGY_BYTE(byte.class, "B", true, ZiggyDataTypeConstants.BYTE_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_INT8, "H5T_NATIVE_INT8") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Byte(((byte[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((byte[]) array1d)[location] = ((Number) arrayMember).byteValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((byte[]) array1d, ((Number) fillValue).byteValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Byte((byte) 0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Byte[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((byte[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].byteValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Byte.toString(((byte[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Byte) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Byte.parseByte(value);
+ }
+ },
+ ZIGGY_SHORT(short.class, "S", true, ZiggyDataTypeConstants.SHORT_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_INT16, "H5T_NATIVE_INT16") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Short(((short[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((short[]) array1d)[location] = ((Number) arrayMember).shortValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((short[]) array1d, ((Number) fillValue).shortValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Short((short) 0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Short[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((short[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].shortValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Short.toString(((short[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Short) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Short.parseShort(value);
+ }
+ },
+ ZIGGY_INT(int.class, "I", true, ZiggyDataTypeConstants.INT_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_INT32, "H5T_NATIVE_INT32") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Integer(((int[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((int[]) array1d)[location] = ((Number) arrayMember).intValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((int[]) array1d, ((Number) fillValue).intValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Integer(0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Integer[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((int[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].intValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Integer.toString(((int[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Integer) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Integer.parseInt(value);
+ }
+ },
+ ZIGGY_LONG(long.class, "J", true, ZiggyDataTypeConstants.LONG_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_INT64, "H5T_NATIVE_INT64") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Long(((long[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((long[]) array1d)[location] = ((Number) arrayMember).longValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((long[]) array1d, ((Number) fillValue).longValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Long(0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Long[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((long[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].longValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Long.toString(((long[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Long) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Long.parseLong(value);
+ }
+ },
+ ZIGGY_FLOAT(float.class, "F", true, ZiggyDataTypeConstants.FLOAT_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_FLOAT, "H5T_NATIVE_FLOAT") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Float(((float[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((float[]) array1d)[location] = ((Number) arrayMember).floatValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((float[]) array1d, ((Number) fillValue).floatValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Float(0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Float[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((float[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].floatValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Float.toString(((float[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Float) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Float.parseFloat(value);
+ }
+ },
+ ZIGGY_DOUBLE(double.class, "D", true, ZiggyDataTypeConstants.DOUBLE_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_DOUBLE, "H5T_NATIVE_DOUBLE") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Double(((double[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((double[]) array1d)[location] = ((Number) arrayMember).doubleValue();
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((double[]) array1d, ((Number) fillValue).doubleValue());
+ }
+
+ @Override
+ public Object boxedZero() {
+ return new Double(0);
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ return ArrayUtils.toPrimitive((Double[]) array1d);
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ return ArrayUtils.toObject((double[]) array1d);
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ for (int i = 0; i < sourceArray.length; i++) {
+ setArrayMember(sourceArray[i].doubleValue(), destinationArray, i);
+ }
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return Double.toString(((double[]) array1d)[index]);
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? ((Double) obj).toString() : "0";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? 0 : Double.parseDouble(value);
+ }
+ },
+ ZIGGY_STRING(String.class, "String", "T", java.lang.String.class, false,
+ ZiggyDataTypeConstants.STRING_TYPE_INT, HDF5Constants.H5T_C_S1, "H5T_C_S1") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return ((String[]) array1d)[location];
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((String[]) array1d)[location] = (String) arrayMember;
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((String[]) array1d, fillValue.toString());
+ }
+
+ @Override
+ public Object boxedZero() {
+ throw new UnsupportedOperationException("Strings do not support boxed zeros");
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ throw new UnsupportedOperationException("Strings cannot be unboxed");
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ throw new UnsupportedOperationException("Strings cannot be boxed");
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ throw new UnsupportedOperationException("Cannot cast numeric array to string array");
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ return ((String[]) array1d)[index];
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ return obj != null ? (String) obj : "";
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ return StringUtils.isEmpty(value) ? "" : value.trim();
+ }
+ },
+ ZIGGY_PERSISTABLE(Persistable.class, "gov.nasa.ziggy.common.persistable.Persistable", "P",
+ Persistable.class, false, ZiggyDataTypeConstants.PERSISTABLE_TYPE_INT,
+ HDF5Constants.H5T_OPAQUE, "H5T_OPAQUE") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return ((Object[]) array1d)[location];
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((Object[]) array1d)[location] = arrayMember;
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ throw new UnsupportedOperationException("Cannot fill Persistable array");
+ }
+
+ @Override
+ public Object boxedZero() {
+ throw new UnsupportedOperationException("Persistables do not support boxed zeros");
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ throw new UnsupportedOperationException("Persistables cannot be unboxed");
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ throw new UnsupportedOperationException("Persistables cannot be boxed");
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ throw new UnsupportedOperationException(
+ "Cannot cast numeric array to Persistable array");
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ throw new UnsupportedOperationException(
+ "Cannot convert persistable array member to string");
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ throw new UnsupportedOperationException("Cannot convert persistable object to string");
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ throw new UnsupportedOperationException("Cannot convert string to Persistable value");
+ }
+ },
+ ZIGGY_ENUM(Enum.class, "E", false, ZiggyDataTypeConstants.ENUM_TYPE_INT,
+ ZiggyDataTypeConstants.ENUM_DUMMY_TYPE, "H5T_C_S1") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return ((Enum>[]) array1d)[location];
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((Enum>[]) array1d)[location] = (Enum>) arrayMember;
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ throw new UnsupportedOperationException("Cannot fill Enum array");
+ }
+
+ @Override
+ public Object boxedZero() {
+ throw new UnsupportedOperationException("Enums do not support boxed zeros");
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ throw new UnsupportedOperationException("Enums cannot be unboxed");
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ throw new UnsupportedOperationException("Enums cannot be boxed");
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ throw new UnsupportedOperationException("Cannot cast numeric array to Enum array");
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ throw new UnsupportedOperationException("Cannot convert Enum array member to string");
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ throw new UnsupportedOperationException("Cannot convert Enum object to string");
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ throw new UnsupportedOperationException("Cannot convert string to Enum value");
+ }
+ },
+ ZIGGY_CHAR(char.class, "C", false, ZiggyDataTypeConstants.CHAR_TYPE_INT,
+ HDF5Constants.H5T_NATIVE_INT16, "H5T_NATIVE_INT16") {
+ @Override
+ protected Object getArrayMember(Object array1d, int location) {
+ return new Character(((char[]) array1d)[location]);
+ }
+
+ @Override
+ public void setArrayMember(Object arrayMember, Object array1d, int location) {
+ ((char[]) array1d)[location] = (Character) arrayMember;
+ }
+
+ @Override
+ protected void fillArray(Object array1d, Object fillValue) {
+ Arrays.fill((char[]) array1d, (Character) fillValue);
+ }
+
+ @Override
+ public Object boxedZero() {
+ throw new UnsupportedOperationException("Chars do not support boxed zeros");
+ }
+
+ @Override
+ protected Object unboxArray(Object array1d) {
+ throw new UnsupportedOperationException("Chars cannot be unboxed");
+ }
+
+ @Override
+ protected Object boxArray(Object array1d) {
+ throw new UnsupportedOperationException("Chars cannot be boxed");
+ }
+
+ @Override
+ protected void unboxAndCastNumericToNumeric(Number[] sourceArray, Object destinationArray) {
+ throw new UnsupportedOperationException("Cannot cast numeric array to char array");
+ }
+
+ @Override
+ public String array1dMemberToString(Object array1d, int index) {
+ throw new UnsupportedOperationException("Cannot convert char array member to string");
+ }
+
+ @Override
+ public String scalarToString(Object obj) {
+ throw new UnsupportedOperationException("Cannot convert char to string");
+ }
+
+ @Override
+ public Object typedValue(String value) {
+ throw new UnsupportedOperationException("Cannot convert string to char value");
+ }
+ };
+
+ private final int attributeTypeInt;
+ private final Class> javaClass;
+ private final String javaClassName;
+ private final String javaTypeName;
+ private final String javaTypeCharacter;
+ private final Class> javaBoxedClass;
+ private final String javaBoxedClassName;
+ private final boolean numeric;
+ private final long hdf5Type;
+ private final String hdf5TypeName;
+
+ ZiggyDataType(Class> javaClass, String javaTypeCharacter, boolean numeric,
+ int attributeTypeInt, long hdf5Type, String hdf5TypeName) {
+ this(javaClass, null, javaTypeCharacter, null, numeric, attributeTypeInt, hdf5Type,
+ hdf5TypeName);
+ }
+
+ ZiggyDataType(Class> javaClass, String javaTypeName, String javaTypeCharacter,
+ Class> javaBoxedClass, boolean numeric, int attributeTypeInt, long hdf5Type,
+ String hdf5TypeName) {
+ this.javaClass = javaClass;
+ javaClassName = javaClass.getName() + ".class";
+ if (javaTypeName != null) {
+ this.javaTypeName = javaTypeName;
+ } else {
+ this.javaTypeName = javaClass.getName();
+ }
+ this.javaTypeCharacter = javaTypeCharacter;
+ if (javaBoxedClass != null) {
+ this.javaBoxedClass = javaBoxedClass;
+ } else {
+ this.javaBoxedClass = Primitives.wrap(javaClass);
+ }
+ javaBoxedClassName = this.javaBoxedClass.getName();
+ this.numeric = numeric;
+ this.attributeTypeInt = attributeTypeInt;
+ this.hdf5Type = hdf5Type;
+ this.hdf5TypeName = hdf5TypeName;
+ }
+
+ protected abstract Object getArrayMember(Object array1d, int location);
+
+ public abstract void setArrayMember(Object arrayMember, Object array1d, int location);
+
+ protected abstract void fillArray(Object array1d, Object fillValue);
+
+ public abstract Object boxedZero();
+
+ protected abstract Object unboxArray(Object array1d);
+
+ protected abstract Object boxArray(Object array1d);
+
+ protected abstract void unboxAndCastNumericToNumeric(Number[] sourceArray,
+ Object destinationArray);
+
+ public abstract String array1dMemberToString(Object array1d, int index);
+
+ public abstract String scalarToString(Object obj);
+
+ public abstract Object typedValue(String value);
+
+ public String getJavaTypeCharacter() {
+ return javaTypeCharacter;
+ }
+
+ public String getJavaClassName() {
+ return javaClassName;
+ }
+
+ public Class> getJavaClass() {
+ return javaClass;
+ }
+
+ public Class> getJavaBoxedClass() {
+ return javaBoxedClass;
+ }
+
+ public String getJavaBoxedClassName() {
+ return javaBoxedClassName;
+ }
+
+ public long getHdf5Type() {
+ return hdf5Type;
+ }
+
+ public String getHdf5TypeName() {
+ return hdf5TypeName;
+ }
+
+ public int getAttributeTypeInt() {
+ return attributeTypeInt;
+ }
+
+ public String getJavaTypeName() {
+ return javaTypeName;
+ }
+
+ public static ZiggyDataType getDataTypeFromAttributeTypeInt(int attributeTypeInt) {
+ for (ZiggyDataType dataType : ZiggyDataType.values()) {
+ if (dataType.attributeTypeInt == attributeTypeInt) {
+ return dataType;
+ }
+ }
+ return null;
+ }
+
+ public static ZiggyDataType getDataTypeFromClassSimpleName(String classSimpleName) {
+ return getDataTypeFromClassSimpleName(classSimpleName, true);
+ }
+
+ public static ZiggyDataType getDataTypeFromClassSimpleName(String classSimpleName,
+ boolean caseSensitive) {
+ String className = classSimpleName;
+ if (!caseSensitive) {
+ className = classSimpleName.toLowerCase();
+ }
+ for (ZiggyDataType dataType : ZiggyDataType.values()) {
+ String javaTypeName = dataType.javaTypeName;
+ if (!caseSensitive) {
+ javaTypeName = dataType.javaTypeName.toLowerCase();
+ }
+ if (javaTypeName.equals(className)) {
+ return dataType;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * A more general converter from string to {@link ZiggyDataType}. This method first attempts to
+ * match the class simple name; if that fails, it attempts to match the name of the enumeration
+ * element. Both of these are performed in a case-insensitive manner.
+ */
+ public static ZiggyDataType getDataTypeFromString(String className) {
+ ZiggyDataType dataType = getDataTypeFromClassSimpleName(className, false);
+ if (dataType == null) {
+ dataType = ZiggyDataType.valueOf(className.toUpperCase());
+ }
+ return dataType;
+ }
+
+ // primitives and primitive arrays cannot be cast to Object, so...
+ public static ZiggyDataType getDataType(boolean value) {
+ return ZIGGY_BOOLEAN;
+ }
+
+ public static ZiggyDataType getDataType(boolean[] value) {
+ return ZIGGY_BOOLEAN;
+ }
+
+ public static ZiggyDataType getDataType(byte value) {
+ return ZIGGY_BYTE;
+ }
+
+ public static ZiggyDataType getDataType(byte[] value) {
+ return ZIGGY_BYTE;
+ }
+
+ public static ZiggyDataType getDataType(short value) {
+ return ZIGGY_SHORT;
+ }
+
+ public static ZiggyDataType getDataType(short[] value) {
+ return ZIGGY_SHORT;
+ }
+
+ public static ZiggyDataType getDataType(int value) {
+ return ZIGGY_INT;
+ }
+
+ public static ZiggyDataType getDataType(int[] value) {
+ return ZIGGY_INT;
+ }
+
+ public static ZiggyDataType getDataType(long value) {
+ return ZIGGY_LONG;
+ }
+
+ public static ZiggyDataType getDataType(long[] value) {
+ return ZIGGY_LONG;
+ }
+
+ public static ZiggyDataType getDataType(float value) {
+ return ZIGGY_FLOAT;
+ }
+
+ public static ZiggyDataType getDataType(float[] value) {
+ return ZIGGY_FLOAT;
+ }
+
+ public static ZiggyDataType getDataType(double value) {
+ return ZIGGY_DOUBLE;
+ }
+
+ public static ZiggyDataType getDataType(double[] value) {
+ return ZIGGY_DOUBLE;
+ }
+
+ /**
+ * Returns the ZiggyDataType object that corresponds to the class of the argument.
+ *
+ * @param object Data object. This can be scalar, array, or list of primitive types, boxed
+ * types, String, Enum, or Persistable.
+ * @return ZiggyDataType corresponding to the underlying data class in the argument, or null for
+ * the case of an empty list.
+ * @throws PipelineException if called with an object of non-valid class (i.e., an object in a
+ * class that does not implement Persistable).
+ */
+ @SuppressWarnings("unchecked")
+ public static ZiggyDataType getDataType(Object object) {
+ ZiggyDataType hdf5Type = null;
+ if (object == null) {
+ return hdf5Type;
+ }
+ if (object instanceof List) {
+ hdf5Type = getDataTypeFromList((List extends Object>) object);
+ } else {
+ hdf5Type = getDataTypeFromClass(object.getClass());
+ }
+ return hdf5Type;
+ }
+
+ private static ZiggyDataType getDataTypeFromList(List extends Object> object) {
+ ZiggyDataType hdf5Type = null;
+ if (object != null && object.size() != 0) {
+ hdf5Type = getDataTypeFromClass(object.get(0).getClass());
+ }
+ return hdf5Type;
+ }
+
+ /**
+ * Get the data type associated with a particular Java class.
+ *
+ * @param clazz
+ * @return ZiggyDataType associated with the class.
+ */
+ static ZiggyDataType getDataTypeFromClass(Class> clazz) {
+ ZiggyDataType hdf5Type = null;
+ String simpleName = clazz.getSimpleName().toLowerCase();
+ int bracketLocation = simpleName.indexOf("[");
+ if (bracketLocation > 0) {
+ simpleName = simpleName.substring(0, bracketLocation);
+ }
+ for (ZiggyDataType dataType : ZiggyDataType.values()) {
+ if (simpleName.equals(dataType.javaTypeName.toLowerCase())) {
+ return dataType;
+ }
+ }
+
+ if (simpleName.equals("integer")) {
+ return ZiggyDataType.ZIGGY_INT;
+ }
+
+ // for enums and Persistables, something slightly slicker is needed
+ try {
+ String className = truncateClassName(clazz.getName());
+ Class> elementalClass = Class.forName(className);
+ if (Enum.class.isAssignableFrom(elementalClass)) {
+ hdf5Type = ZIGGY_ENUM;
+ } else if (Persistable.class.isAssignableFrom(elementalClass)) {
+ hdf5Type = ZIGGY_PERSISTABLE;
+ }
+ } catch (ClassNotFoundException e) {
+ throw new PipelineException(
+ "Unable to perform forName operation for class " + simpleName, e);
+ }
+
+ // if it's still null, then this isn't something we can persist...
+
+ if (hdf5Type == null) {
+ throw new PipelineException(
+ "Unable to determine data type of object with class " + clazz.getName());
+ }
+ return hdf5Type;
+ }
+
+ /**
+ * Strips the "L" and "[" leading characters, and ";" trailing character, off a class name.
+ */
+ public static String truncateClassName(String fullClassName) {
+ while (fullClassName.startsWith("[") || fullClassName.startsWith("L")) {
+ fullClassName = fullClassName.substring(1);
+ }
+ if (fullClassName.endsWith(";")) {
+ fullClassName = fullClassName.substring(0, fullClassName.length() - 1);
+ }
+ return fullClassName;
+ }
+
+ /**
+ * Determines the ZiggyDataType of a {@link Field}.
+ */
+ public static ZiggyDataType getDataType(Field field) {
+ field.setAccessible(true);
+ Class> clazz = null;
+ Class> fieldClass = field.getType();
+ try {
+ if (fieldClass.getName().equals("java.util.List")) {
+ Type type = field.getGenericType();
+ ParameterizedType pType = (ParameterizedType) type;
+ String className = pType.getActualTypeArguments()[0].getTypeName();
+ clazz = Class.forName(className);
+ } else {
+ clazz = fieldClass;
+ }
+ return getDataTypeFromClass(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new PipelineException("Unable to determine HDF5 class of " + fieldClass.getName(),
+ e);
+ }
+ }
+
+ /**
+ * Returns the contents of a {@link Field}, as a {@link String}. Assumes that the type of the
+ * {@link Field} is one of the {@link ZiggyDataType} types, or a 1-dimensional array of same.
+ *
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ public static String objectToString(Object obj, Field field)
+ throws IllegalArgumentException, IllegalAccessException {
+ field.setAccessible(true);
+ return objectToString(field.get(obj), ZiggyDataType.getDataType(field),
+ field.getType().isArray());
+ }
+
+ public static String objectToString(Object obj) {
+ return objectToString(obj, ZiggyDataType.getDataType(obj), obj.getClass().isArray());
+ }
+
+ /**
+ * Converts an {@link Object} to a {@link String}. The argument must be an instance of one of
+ * the {@link ZiggyDataType} types, or an array of same. If the object is an array, a string
+ * containing a comma-separated list of the array's values will be returned.
+ */
+ public static String objectToString(Object obj, ZiggyDataType dataType, boolean isArray) {
+
+ if (isArray) {
+ return arrayToString(obj, dataType);
+ } else {
+ return dataType.scalarToString(obj);
+ }
+ }
+
+ /**
+ * Converts an array of values to a {@link String} in which the values are comma-separated.
+ */
+ private static String arrayToString(Object arrayObject, ZiggyDataType dataType) {
+ StringBuilder sb = new StringBuilder();
+ long[] arraySize = arrayObject != null ? ZiggyArrayUtils.getArraySize(arrayObject)
+ : new long[] { 0 };
+ if (arraySize.length > 1) {
+ throw new IllegalArgumentException("Argument to arrayToString must be 1-d array");
+ }
+ for (int arrayIndex = 0; arrayIndex < arraySize[0]; arrayIndex++) {
+ sb.append(dataType.array1dMemberToString(arrayObject, arrayIndex));
+ sb.append(",");
+ }
+ if (sb.length() > 0) {
+ sb.setLength(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Sets the value of a {@link Field} in an {@link Object}. The {@param value} argument is
+ * converted from a {@link String} to a scalar or 1-d array of values of the appropriate type.
+ */
+ public static void setField(Object obj, Field field, String value)
+ throws IllegalArgumentException, IllegalAccessException {
+ ZiggyDataType dataType = getDataType(field);
+ boolean isArray = field.getType().isArray();
+ field.setAccessible(true);
+ field.set(obj, stringToObject(value, dataType, isArray));
+ }
+
+ /**
+ * Converts a {@link String} to a scalar value or an array of values, with the type given by an
+ * instance of {@link ZiggyDataType}.
+ */
+ public static Object stringToObject(String value, ZiggyDataType dataType, boolean isArray) {
+
+ if (!isArray) {
+ return dataType.typedValue(value);
+ } else {
+ return ZiggyArrayUtils.stringToArray(value, dataType);
+ }
+ }
+
+ public static int elementSizeBytes(ZiggyDataType dataType)
+ throws IllegalArgumentException, IllegalAccessException {
+ int elementSize = 1;
+ Class> boxedClass = dataType.javaBoxedClass;
+ for (Field field : boxedClass.getFields()) {
+ if (field.getName().contentEquals("BYTES")) {
+ elementSize = field.getInt(null);
+ break;
+ }
+ }
+ return elementSize;
+ }
+
+ public boolean isNumeric() {
+ return numeric;
+ }
+
+ public static Object unbox1dArray(Object boxedArray) {
+ return getDataType(boxedArray).unboxArray(boxedArray);
+ }
+
+ public static Object box1dArray(Object boxedArray) {
+ return getDataType(boxedArray).boxArray(boxedArray);
+ }
+
+ public static void castBoxedNumericToUnboxedNumeric(Number[] sourceArray,
+ Object destinationArray) {
+ getDataType(destinationArray).unboxAndCastNumericToNumeric(sourceArray, destinationArray);
+ }
+
+ public static void fill1dArray(Object array1d, Object fillValue) {
+ getDataType(array1d).fillArray(array1d, fillValue);
+ }
+
+ public static void set1dArrayMember(Object arrayMember, Object array1d, int location) {
+ getDataType(array1d).setArrayMember(arrayMember, array1d, location);
+ }
+
+ public static Object get1dArrayMember(Object array1d, int location) {
+ return getDataType(array1d).getArrayMember(array1d, location);
+ }
+
+ public static class ZiggyDataTypeConstants {
+
+ // We need unique HDF5 type ints for some types that aren't actually used in our HDF5
+ // code. To avoid conflicts with actual HDF5 type ints, we will use negatives for this
+ private static final long BOOLEAN_DUMMY_TYPE = -1;
+ private static final long ENUM_DUMMY_TYPE = -2;
+
+ // We can't rely upon the integers provided by HDF5 to be the same from one system to
+ // another, so we need to provide our own set of HDF5 type numbers that can be put into
+ // an attribute by one application and read by another
+
+ public static final int BOOLEAN_TYPE_INT = 1;
+ public static final int BYTE_TYPE_INT = 2;
+ public static final int SHORT_TYPE_INT = 3;
+ public static final int INT_TYPE_INT = 4;
+ public static final int LONG_TYPE_INT = 5;
+ public static final int FLOAT_TYPE_INT = 6;
+ public static final int DOUBLE_TYPE_INT = 7;
+ public static final int STRING_TYPE_INT = 8;
+ public static final int PERSISTABLE_TYPE_INT = 9;
+ public static final int ENUM_TYPE_INT = 10;
+ public static final int CHAR_TYPE_INT = 11;
+ }
+}
diff --git a/src/main/java/gov/nasa/ziggy/collections/package.html b/src/main/java/gov/nasa/ziggy/collections/package.html
new file mode 100644
index 0000000..dddca94
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/collections/package.html
@@ -0,0 +1,14 @@
+
+
+
+ The Collections package provides support for Ziggy data operations that
+ occur across arrays, lists, or other collections of data objects.
+
+
+ Author
+ Bill Wohler
+ PT
+
+
+
+
diff --git a/src/main/java/gov/nasa/ziggy/crud/AbstractCrud.java b/src/main/java/gov/nasa/ziggy/crud/AbstractCrud.java
new file mode 100644
index 0000000..1f6d4c1
--- /dev/null
+++ b/src/main/java/gov/nasa/ziggy/crud/AbstractCrud.java
@@ -0,0 +1,392 @@
+package gov.nasa.ziggy.crud;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.hibernate.Criteria;
+import org.hibernate.Query;
+import org.hibernate.SQLQuery;
+import org.hibernate.Session;
+import org.hibernate.criterion.Disjunction;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.Restrictions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+import gov.nasa.ziggy.collections.ListChunkIterator;
+import gov.nasa.ziggy.pipeline.definition.ExternalIdAssignable;
+import gov.nasa.ziggy.services.database.DatabaseService;
+
+/**
+ * The parent class for all CRUD classes.
+ *
+ * NB: for CRUD operations on classes that include an originator, use AbstractProducerConsumerCrud
+ * as the parent class, since that class automates the management of the producer-consumer
+ * relationship.
+ *
+ * @author Bill Wohler
+ * @author PT
+ */
+public abstract class AbstractCrud implements AbstractCrudInterface {
+ private static final Logger log = LoggerFactory.getLogger(AbstractCrud.class);
+
+ /**
+ * This is the maximum number of dynamically-created expressions sent to the database. This
+ * limit is 1000 in Oracle. A setting of 950 leaves plenty of room for other expressions in the
+ * query.
+ *
+ * @see ListChunkIterator
+ */
+ public static final int MAX_EXPRESSIONS = 950;
+
+ private DatabaseService databaseService;
+ private boolean readOnly;
+
+ /**
+ * Creates a {@link AbstractCrud} whose read-only property is set to {@code false}.
+ */
+ protected AbstractCrud() {
+ }
+
+ /**
+ * Creates a {@link AbstractCrud} with the given read-only property. CRUD classes can
+ * instantiate themselves with this parameter set to {@code true} to avoid dirty checking and
+ * therefore save on CPU usage.
+ *
+ * Use {@link #createQuery(String)} to take advantage of this property.
+ */
+ protected AbstractCrud(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * Creates a {@link AbstractCrud} with the given database service whose read-only property is
+ * set to {@code false}.
+ */
+ protected AbstractCrud(DatabaseService databaseService) {
+ this.databaseService = databaseService;
+ }
+
+ /**
+ * Creates a {@link AbstractCrud} with the given database service and read-only property. CRUD
+ * classes can instantiate themselves this parameter set to {@code true} to avoid dirty checking
+ * and therefore save on CPU usage.
+ *
+ * Use {@link #createQuery(String)} to take advantage of this property.
+ */
+ protected AbstractCrud(DatabaseService databaseService, boolean readOnly) {
+ this(databaseService);
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * Returns the database service used by this CRUD object.
+ */
+ protected final DatabaseService getDatabaseService() {
+ if (databaseService == null) {
+ databaseService = DatabaseService.getInstance();
+ }
+
+ return databaseService;
+ }
+
+ /**
+ * Convenience method that returns the current persistence session. Do not cache this locally as
+ * it can vary between threads.
+ *
+ * @return the persistence session.
+ */
+ protected final Session getSession() {
+ return getDatabaseService().getSession();
+ }
+
+ /**
+ * Retrieves an object from the database with given class and ID.
+ *
+ * @param clazz the object class
+ * @param id the object ID
+ * @return the retrieved object
+ */
+ @SuppressWarnings("unchecked")
+ public final T get(Class clazz, Serializable id) {
+ Object r = getSession().get(clazz, id);
+ return (T) r;
+ }
+
+ /**
+ * Removes an object from the database layer session cache.
+ *
+ * @param o the object to remove from the session cache
+ */
+ public void evict(Object o) {
+ getSession().evict(o);
+ }
+
+ /**
+ * Makes an object persistent.
+ *