stevedlawrence commented on code in PR #183:
URL: https://github.com/apache/daffodil-sbt/pull/183#discussion_r3266273271


##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +854,252 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+
+    /**
+     * Whether or not to publish the flattened schemas zip. Defaults to false.
+     *
+     * If projects want to publish flattened schemas then they must explicitly 
enable it by
+     * setting 'daffodilFlattenSchemas / publishArtifact := true'.
+     *
+     * If false, flattened schemas will not be created unless you explicitly 
run the
+     * daffodilFlattenSchemas.
+     */
+    daffodilFlattenSchemas / publishArtifact := false,
+
+    daffodilFlattenSchemas / artifact := Artifact(
+      name.value,
+      "flat",
+      "zip",
+      Some("flat"),
+      Vector(),
+      None
+    ),
+
+    /**
+     * Only grab the file types that need to be flattened. These files are only
+     * grabbed from the root project. Any referenced files will be pulled in
+     * from the classpath. Note that XML files are commonly used in XSLT for
+     * settings.
+     */
+    daffodilFlattenSchemas / includeFilter := "*.xsd" | "*.xsl" | "*.xslt" | 
"*.xml",
+    daffodilFlattenSchemas / excludeFilter := HiddenFileFilter,
+
+    daffodilFlattenResourceReferencePatterns := List(
+      """(?<!xsi:)schemaLocation=\"([^\"]*)\"""".r,
+      "href=\"([^\"]*)\"".r,
+      "document[(]'([^']*)'[)]".r
+    ),
+
+    daffodilFlattenSchemas / products := {
+
+      val logger = streams.value.log
+      val filter =
+        (daffodilFlattenSchemas / includeFilter).value -- 
(daffodilFlattenSchemas / excludeFilter).value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectURLs = (Compile / resourceDirectories).value.map { root =>
+        (root ** filter).get.map(_.toURI.toURL).toList
+      }.flatten
+
+      /**
+       * Create a URLClassLoader object with URLs to all resources used by the
+       * project. The class loader will be used to resolve references made
+       * within the flattened files.
+       */
+      val allClasspathURLs = (Test / 
externalDependencyClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val referenceRegexes = daffodilFlattenResourceReferencePatterns.value
+      val jarRegex = "(.*/(.*).jar)!(.*)".r
+
+      val seen = scala.collection.mutable.Set[URI]()
+      val unprocessed = scala.collection.mutable.Stack[URI]()
+      val unresolved = scala.collection.mutable.ArrayBuffer[(URI, String)]()
+      val jarFileSystems = scala.collection.mutable.Map[String, FileSystem]()
+      unprocessed.pushAll(projectURLs.map(_.toURI))
+      seen ++= projectURLs.map(_.toURI)
+      while (!unprocessed.isEmpty) {
+        val contextURI = unprocessed.pop
+        val bytes = contextURI.toURL.openStream().readAllBytes()
+
+        // Get a Java Path for the current file (whether it is in a JAR or
+        // just a regular file) and a Path for where the current file will be
+        // moved to in the flattened directory structure.  Note that using a
+        // Java Path on a regular file will use the default FileSystem while 
for
+        // JARs a FileSystem is created for interacting with the files inside
+        // the JAR. This allows all Path functions like resolveSibling/exists 
to
+        // work with both regular files or files contained inside a JAR.
+        val (contextPath, flatPath) = contextURI.getScheme match {
+          case "jar" =>
+            contextURI.toString match {
+              case jarRegex(jarPath, jarName, path) => {
+                val fs = jarFileSystems.getOrElseUpdate(
+                  Paths.get(jarPath).toString,
+                  FileSystems.newFileSystem(contextURI, new 
java.util.HashMap[String, Any]())
+                )
+                val cPath = fs.getPath(path)
+                (
+                  cPath,
+                  Paths.get(
+                    flatDir.toString,
+                    s"${jarName}__${cPath.toString.tail.replaceAll("/", "__")}"
+                  )
+                )
+              }
+              case _ =>
+                throw new IllegalArgumentException(s"Unable to parse JAR URI: 
$contextURI")
+            }
+          case "file" => {
+            val path = Paths.get(contextURI)
+            val root = (Compile / resourceDirectories).value
+              .find(dir => contextURI.getPath.startsWith(dir.toString))
+              .get
+              .toURI
+            (
+              path,
+              Paths.get(
+                flatDir.toString,
+                Paths.get(root).relativize(path).toString.replaceAll("/", "__")
+              )
+            )
+          }
+          case _ =>
+            throw new IllegalArgumentException(s"Unrecognized URI scheme: 
$contextURI")
+        }
+
+        assert(
+          !Files.exists(flatPath),
+          s"File $flatPath already exists and would be overwritten, aborting!"
+        )
+        val bw = Files.newBufferedWriter(flatPath)
+        val fileAsString = new String(bytes, Charset.defaultCharset())
+
+        val references = referenceRegexes.flatMap { re =>
+          re.findAllIn(fileAsString).matchData.map(_.group(1))
+        }
+
+        val resolvedRefs = references.flatMap { ref =>
+          val optResolved = if (ref.startsWith("/")) {
+            // Dealing with an absolute path
+            Option(classLoader.findResource(ref.tail))
+          } else {
+            // Relative path
+            val refPath = contextPath.resolveSibling(ref).normalize()
+            if (Files.exists(refPath)) {
+              // Referenced path exists in either the same root resource
+              // diretory or jar file as the context schema
+              Some(refPath.toUri.toURL)
+            } else {
+              contextURI.getScheme match {
+                case "file" => {
+                  // Need to check other resource directories
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    Files.exists(Paths.get(root.toURI.resolve(ref)))

Review Comment:
   I took another look at this, and I think this needs a tweak. As an example 
of the issue I'm seeing, say we have foo.dfdl.xsd at this path:
   
   ```
   file:///format/resourceDirectoryA/com/example/foo.dfdl.xsd
   ```
   
   And say that file has a reference to schemaLocation="bar.dfdl.xsd". Because 
that is a relative path, it should be resolved relative to the above URI, and 
so we expect it to be at
   
   ```
   file:///format/resourceDirectoryA/com/example/bar.dfdl.xsd
   ```
   
   But if bar.dfdl.xsd was in another resource directory, it would still have 
the com/example/bar.dfdl.xsd path, just rooted in a different directory, e.g.
   
   ```
   file:///format/resourceDirectoryB/com/example/bar.dfdl.xsd
   ```
   
   So it feels like what we need to is to strip off the root directory from 
contextURI, which would give us something like "com/example/foo.dfdl.xsd", 
resolve our reference relative to that, which would give us 
"com/example/bar.dfdl.xsd", and then to this code to find that path relative to 
all of our possible resource directories. Might be other ways to do it, but 
some thing along those lines.



##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +854,252 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+
+    /**
+     * Whether or not to publish the flattened schemas zip. Defaults to false.
+     *
+     * If projects want to publish flattened schemas then they must explicitly 
enable it by
+     * setting 'daffodilFlattenSchemas / publishArtifact := true'.
+     *
+     * If false, flattened schemas will not be created unless you explicitly 
run the
+     * daffodilFlattenSchemas.
+     */
+    daffodilFlattenSchemas / publishArtifact := false,
+
+    daffodilFlattenSchemas / artifact := Artifact(
+      name.value,
+      "flat",
+      "zip",
+      Some("flat"),
+      Vector(),
+      None
+    ),
+
+    /**
+     * Only grab the file types that need to be flattened. These files are only
+     * grabbed from the root project. Any referenced files will be pulled in
+     * from the classpath. Note that XML files are commonly used in XSLT for
+     * settings.
+     */
+    daffodilFlattenSchemas / includeFilter := "*.xsd" | "*.xsl" | "*.xslt" | 
"*.xml",
+    daffodilFlattenSchemas / excludeFilter := HiddenFileFilter,
+
+    daffodilFlattenResourceReferencePatterns := List(
+      """(?<!xsi:)schemaLocation=\"([^\"]*)\"""".r,
+      "href=\"([^\"]*)\"".r,
+      "document[(]'([^']*)'[)]".r
+    ),
+
+    daffodilFlattenSchemas / products := {
+
+      val logger = streams.value.log
+      val filter =
+        (daffodilFlattenSchemas / includeFilter).value -- 
(daffodilFlattenSchemas / excludeFilter).value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectURLs = (Compile / resourceDirectories).value.map { root =>
+        (root ** filter).get.map(_.toURI.toURL).toList
+      }.flatten
+
+      /**
+       * Create a URLClassLoader object with URLs to all resources used by the
+       * project. The class loader will be used to resolve references made
+       * within the flattened files.
+       */
+      val allClasspathURLs = (Test / 
externalDependencyClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val referenceRegexes = daffodilFlattenResourceReferencePatterns.value
+      val jarRegex = "(.*/(.*).jar)!(.*)".r
+
+      val seen = scala.collection.mutable.Set[URI]()
+      val unprocessed = scala.collection.mutable.Stack[URI]()
+      val unresolved = scala.collection.mutable.ArrayBuffer[(URI, String)]()
+      val jarFileSystems = scala.collection.mutable.Map[String, FileSystem]()
+      unprocessed.pushAll(projectURLs.map(_.toURI))
+      seen ++= projectURLs.map(_.toURI)
+      while (!unprocessed.isEmpty) {
+        val contextURI = unprocessed.pop
+        val bytes = contextURI.toURL.openStream().readAllBytes()
+
+        // Get a Java Path for the current file (whether it is in a JAR or
+        // just a regular file) and a Path for where the current file will be
+        // moved to in the flattened directory structure.  Note that using a
+        // Java Path on a regular file will use the default FileSystem while 
for
+        // JARs a FileSystem is created for interacting with the files inside
+        // the JAR. This allows all Path functions like resolveSibling/exists 
to
+        // work with both regular files or files contained inside a JAR.
+        val (contextPath, flatPath) = contextURI.getScheme match {
+          case "jar" =>
+            contextURI.toString match {
+              case jarRegex(jarPath, jarName, path) => {
+                val fs = jarFileSystems.getOrElseUpdate(
+                  Paths.get(jarPath).toString,
+                  FileSystems.newFileSystem(contextURI, new 
java.util.HashMap[String, Any]())
+                )
+                val cPath = fs.getPath(path)
+                (
+                  cPath,
+                  Paths.get(
+                    flatDir.toString,
+                    s"${jarName}__${cPath.toString.tail.replaceAll("/", "__")}"
+                  )
+                )
+              }
+              case _ =>
+                throw new IllegalArgumentException(s"Unable to parse JAR URI: 
$contextURI")
+            }
+          case "file" => {
+            val path = Paths.get(contextURI)
+            val root = (Compile / resourceDirectories).value
+              .find(dir => contextURI.getPath.startsWith(dir.toString))
+              .get
+              .toURI
+            (
+              path,
+              Paths.get(
+                flatDir.toString,
+                Paths.get(root).relativize(path).toString.replaceAll("/", "__")
+              )
+            )
+          }
+          case _ =>
+            throw new IllegalArgumentException(s"Unrecognized URI scheme: 
$contextURI")
+        }
+
+        assert(
+          !Files.exists(flatPath),
+          s"File $flatPath already exists and would be overwritten, aborting!"
+        )
+        val bw = Files.newBufferedWriter(flatPath)
+        val fileAsString = new String(bytes, Charset.defaultCharset())
+
+        val references = referenceRegexes.flatMap { re =>
+          re.findAllIn(fileAsString).matchData.map(_.group(1))
+        }
+
+        val resolvedRefs = references.flatMap { ref =>
+          val optResolved = if (ref.startsWith("/")) {
+            // Dealing with an absolute path
+            Option(classLoader.findResource(ref.tail))
+          } else {
+            // Relative path
+            val refPath = contextPath.resolveSibling(ref).normalize()
+            if (Files.exists(refPath)) {
+              // Referenced path exists in either the same root resource
+              // diretory or jar file as the context schema
+              Some(refPath.toUri.toURL)
+            } else {
+              contextURI.getScheme match {
+                case "file" => {
+                  // Need to check other resource directories
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    Files.exists(Paths.get(root.toURI.resolve(ref)))
+                  )
+                  if (resolvedRoot.isDefined)
+                    Some(resolvedRoot.get.toURI.resolve(ref).toURL)
+                  else {
+                    // Maybe this path is actually absolute, just missing the
+                    // leading '/', check the classpath
+                    Option(classLoader.findResource(ref))
+                  }
+                }
+                case "jar" => {
+                  // Maybe this path in the JAR is actually absolute, just
+                  // missing the leading '/', try resolving it relative to the
+                  // root of the JAR
+                  val jarPath = contextPath.getRoot().resolve(ref)
+                  if (Files.exists(jarPath))
+                    Some(jarPath.toUri.toURL)
+                  else
+                    None
+                }
+                case _ => None
+              }
+            }
+          }
+          if (optResolved.isEmpty)
+            unresolved.append((contextURI, ref))
+          optResolved.map(resolved => ref -> resolved)
+        }.toMap
+
+        val updatedFileAsString = {
+          resolvedRefs.foldLeft(fileAsString) {
+            case (input, (ref, optResolvedURL)) => {
+              // For each reference replace each instance of it in the
+              // file with the same reference but with "/" changed to "__"
+              val resolvedURI = optResolvedURL.toURI
+              val relativized = resolvedURI.getScheme match {
+                case "jar" =>
+                  resolvedURI.toString match {
+                    case jarRegex(_, jarName, path) =>
+                      s"${jarName}__${path.tail.replaceAll("/", "__")}"
+                  }
+                case _ => {
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    resolvedURI.toString.contains(root.toString)
+                  )
+                  resolvedRoot.get.toURI.relativize(resolvedURI).toString
+                }
+              }
+              input.replaceAll(ref, relativized.replaceAll("/", "__"))
+            }
+          }
+        }
+
+        bw.write(updatedFileAsString)
+        bw.close()
+        val unseen = resolvedRefs.values.map(_.toURI).filter(seen.add(_))
+        unprocessed.pushAll(unseen)
+      }
+
+      // Close any open JAR FileSystems
+      jarFileSystems.values.foreach(_.close())
+
+      // Error unresolved references
+      unresolved.foreach { case (context, ref) =>
+        logger.error(s"Unable to resolve reference to $ref from source file 
$context")
+      }
+
+      // Error out if we have any unresolved references
+      assert(unresolved.isEmpty)

Review Comment:
   This is an error that could indicate a broken schema, so it feels like more 
of a user error than an assert. In SBT that way to provider user level error 
messages is with a MessageOnlyException. So this should be something like:
   
   ```scala
   if (!unresolved.isEmpty)
     throw new MessageOnlyException("Unable to resolve one or more references 
while flattening")
   ```



##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +854,252 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+
+    /**
+     * Whether or not to publish the flattened schemas zip. Defaults to false.
+     *
+     * If projects want to publish flattened schemas then they must explicitly 
enable it by
+     * setting 'daffodilFlattenSchemas / publishArtifact := true'.
+     *
+     * If false, flattened schemas will not be created unless you explicitly 
run the
+     * daffodilFlattenSchemas.
+     */
+    daffodilFlattenSchemas / publishArtifact := false,
+
+    daffodilFlattenSchemas / artifact := Artifact(
+      name.value,
+      "flat",
+      "zip",
+      Some("flat"),
+      Vector(),
+      None
+    ),
+
+    /**
+     * Only grab the file types that need to be flattened. These files are only
+     * grabbed from the root project. Any referenced files will be pulled in
+     * from the classpath. Note that XML files are commonly used in XSLT for
+     * settings.
+     */
+    daffodilFlattenSchemas / includeFilter := "*.xsd" | "*.xsl" | "*.xslt" | 
"*.xml",
+    daffodilFlattenSchemas / excludeFilter := HiddenFileFilter,
+
+    daffodilFlattenResourceReferencePatterns := List(
+      """(?<!xsi:)schemaLocation=\"([^\"]*)\"""".r,
+      "href=\"([^\"]*)\"".r,
+      "document[(]'([^']*)'[)]".r
+    ),
+
+    daffodilFlattenSchemas / products := {
+
+      val logger = streams.value.log
+      val filter =
+        (daffodilFlattenSchemas / includeFilter).value -- 
(daffodilFlattenSchemas / excludeFilter).value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectURLs = (Compile / resourceDirectories).value.map { root =>
+        (root ** filter).get.map(_.toURI.toURL).toList
+      }.flatten
+
+      /**
+       * Create a URLClassLoader object with URLs to all resources used by the
+       * project. The class loader will be used to resolve references made
+       * within the flattened files.
+       */
+      val allClasspathURLs = (Test / 
externalDependencyClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val referenceRegexes = daffodilFlattenResourceReferencePatterns.value
+      val jarRegex = "(.*/(.*).jar)!(.*)".r
+
+      val seen = scala.collection.mutable.Set[URI]()
+      val unprocessed = scala.collection.mutable.Stack[URI]()
+      val unresolved = scala.collection.mutable.ArrayBuffer[(URI, String)]()
+      val jarFileSystems = scala.collection.mutable.Map[String, FileSystem]()
+      unprocessed.pushAll(projectURLs.map(_.toURI))
+      seen ++= projectURLs.map(_.toURI)
+      while (!unprocessed.isEmpty) {
+        val contextURI = unprocessed.pop
+        val bytes = contextURI.toURL.openStream().readAllBytes()
+
+        // Get a Java Path for the current file (whether it is in a JAR or
+        // just a regular file) and a Path for where the current file will be
+        // moved to in the flattened directory structure.  Note that using a
+        // Java Path on a regular file will use the default FileSystem while 
for
+        // JARs a FileSystem is created for interacting with the files inside
+        // the JAR. This allows all Path functions like resolveSibling/exists 
to
+        // work with both regular files or files contained inside a JAR.
+        val (contextPath, flatPath) = contextURI.getScheme match {
+          case "jar" =>
+            contextURI.toString match {
+              case jarRegex(jarPath, jarName, path) => {
+                val fs = jarFileSystems.getOrElseUpdate(
+                  Paths.get(jarPath).toString,
+                  FileSystems.newFileSystem(contextURI, new 
java.util.HashMap[String, Any]())
+                )
+                val cPath = fs.getPath(path)
+                (
+                  cPath,
+                  Paths.get(
+                    flatDir.toString,
+                    s"${jarName}__${cPath.toString.tail.replaceAll("/", "__")}"
+                  )
+                )
+              }
+              case _ =>
+                throw new IllegalArgumentException(s"Unable to parse JAR URI: 
$contextURI")
+            }
+          case "file" => {
+            val path = Paths.get(contextURI)
+            val root = (Compile / resourceDirectories).value
+              .find(dir => contextURI.getPath.startsWith(dir.toString))
+              .get
+              .toURI
+            (
+              path,
+              Paths.get(
+                flatDir.toString,
+                Paths.get(root).relativize(path).toString.replaceAll("/", "__")
+              )
+            )
+          }
+          case _ =>
+            throw new IllegalArgumentException(s"Unrecognized URI scheme: 
$contextURI")
+        }
+
+        assert(
+          !Files.exists(flatPath),
+          s"File $flatPath already exists and would be overwritten, aborting!"
+        )
+        val bw = Files.newBufferedWriter(flatPath)
+        val fileAsString = new String(bytes, Charset.defaultCharset())
+
+        val references = referenceRegexes.flatMap { re =>
+          re.findAllIn(fileAsString).matchData.map(_.group(1))
+        }
+
+        val resolvedRefs = references.flatMap { ref =>
+          val optResolved = if (ref.startsWith("/")) {
+            // Dealing with an absolute path
+            Option(classLoader.findResource(ref.tail))
+          } else {
+            // Relative path
+            val refPath = contextPath.resolveSibling(ref).normalize()
+            if (Files.exists(refPath)) {
+              // Referenced path exists in either the same root resource
+              // diretory or jar file as the context schema
+              Some(refPath.toUri.toURL)
+            } else {
+              contextURI.getScheme match {
+                case "file" => {
+                  // Need to check other resource directories
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    Files.exists(Paths.get(root.toURI.resolve(ref)))
+                  )
+                  if (resolvedRoot.isDefined)
+                    Some(resolvedRoot.get.toURI.resolve(ref).toURL)
+                  else {
+                    // Maybe this path is actually absolute, just missing the
+                    // leading '/', check the classpath
+                    Option(classLoader.findResource(ref))
+                  }
+                }
+                case "jar" => {
+                  // Maybe this path in the JAR is actually absolute, just
+                  // missing the leading '/', try resolving it relative to the
+                  // root of the JAR
+                  val jarPath = contextPath.getRoot().resolve(ref)
+                  if (Files.exists(jarPath))
+                    Some(jarPath.toUri.toURL)
+                  else
+                    None
+                }
+                case _ => None
+              }
+            }
+          }
+          if (optResolved.isEmpty)
+            unresolved.append((contextURI, ref))
+          optResolved.map(resolved => ref -> resolved)
+        }.toMap
+
+        val updatedFileAsString = {
+          resolvedRefs.foldLeft(fileAsString) {
+            case (input, (ref, optResolvedURL)) => {
+              // For each reference replace each instance of it in the
+              // file with the same reference but with "/" changed to "__"
+              val resolvedURI = optResolvedURL.toURI
+              val relativized = resolvedURI.getScheme match {
+                case "jar" =>
+                  resolvedURI.toString match {
+                    case jarRegex(_, jarName, path) =>
+                      s"${jarName}__${path.tail.replaceAll("/", "__")}"
+                  }
+                case _ => {
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    resolvedURI.toString.contains(root.toString)

Review Comment:
   Above you updated something similar to `contextURI.getPath.startsWith`, can 
this do something similar for consistency?



##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +854,252 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+
+    /**
+     * Whether or not to publish the flattened schemas zip. Defaults to false.
+     *
+     * If projects want to publish flattened schemas then they must explicitly 
enable it by
+     * setting 'daffodilFlattenSchemas / publishArtifact := true'.
+     *
+     * If false, flattened schemas will not be created unless you explicitly 
run the
+     * daffodilFlattenSchemas.
+     */
+    daffodilFlattenSchemas / publishArtifact := false,
+
+    daffodilFlattenSchemas / artifact := Artifact(
+      name.value,
+      "flat",
+      "zip",
+      Some("flat"),
+      Vector(),
+      None
+    ),
+
+    /**
+     * Only grab the file types that need to be flattened. These files are only
+     * grabbed from the root project. Any referenced files will be pulled in
+     * from the classpath. Note that XML files are commonly used in XSLT for
+     * settings.
+     */
+    daffodilFlattenSchemas / includeFilter := "*.xsd" | "*.xsl" | "*.xslt" | 
"*.xml",
+    daffodilFlattenSchemas / excludeFilter := HiddenFileFilter,
+
+    daffodilFlattenResourceReferencePatterns := List(
+      """(?<!xsi:)schemaLocation=\"([^\"]*)\"""".r,
+      "href=\"([^\"]*)\"".r,
+      "document[(]'([^']*)'[)]".r
+    ),
+
+    daffodilFlattenSchemas / products := {
+
+      val logger = streams.value.log
+      val filter =
+        (daffodilFlattenSchemas / includeFilter).value -- 
(daffodilFlattenSchemas / excludeFilter).value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectURLs = (Compile / resourceDirectories).value.map { root =>
+        (root ** filter).get.map(_.toURI.toURL).toList
+      }.flatten
+
+      /**
+       * Create a URLClassLoader object with URLs to all resources used by the
+       * project. The class loader will be used to resolve references made
+       * within the flattened files.
+       */
+      val allClasspathURLs = (Test / 
externalDependencyClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val referenceRegexes = daffodilFlattenResourceReferencePatterns.value
+      val jarRegex = "(.*/(.*).jar)!(.*)".r
+
+      val seen = scala.collection.mutable.Set[URI]()
+      val unprocessed = scala.collection.mutable.Stack[URI]()
+      val unresolved = scala.collection.mutable.ArrayBuffer[(URI, String)]()
+      val jarFileSystems = scala.collection.mutable.Map[String, FileSystem]()
+      unprocessed.pushAll(projectURLs.map(_.toURI))
+      seen ++= projectURLs.map(_.toURI)
+      while (!unprocessed.isEmpty) {
+        val contextURI = unprocessed.pop
+        val bytes = contextURI.toURL.openStream().readAllBytes()
+
+        // Get a Java Path for the current file (whether it is in a JAR or
+        // just a regular file) and a Path for where the current file will be
+        // moved to in the flattened directory structure.  Note that using a
+        // Java Path on a regular file will use the default FileSystem while 
for
+        // JARs a FileSystem is created for interacting with the files inside
+        // the JAR. This allows all Path functions like resolveSibling/exists 
to
+        // work with both regular files or files contained inside a JAR.
+        val (contextPath, flatPath) = contextURI.getScheme match {
+          case "jar" =>
+            contextURI.toString match {
+              case jarRegex(jarPath, jarName, path) => {
+                val fs = jarFileSystems.getOrElseUpdate(
+                  Paths.get(jarPath).toString,
+                  FileSystems.newFileSystem(contextURI, new 
java.util.HashMap[String, Any]())
+                )
+                val cPath = fs.getPath(path)
+                (
+                  cPath,
+                  Paths.get(
+                    flatDir.toString,
+                    s"${jarName}__${cPath.toString.tail.replaceAll("/", "__")}"
+                  )
+                )
+              }
+              case _ =>
+                throw new IllegalArgumentException(s"Unable to parse JAR URI: 
$contextURI")
+            }
+          case "file" => {
+            val path = Paths.get(contextURI)
+            val root = (Compile / resourceDirectories).value
+              .find(dir => contextURI.getPath.startsWith(dir.toString))
+              .get
+              .toURI
+            (
+              path,
+              Paths.get(
+                flatDir.toString,
+                Paths.get(root).relativize(path).toString.replaceAll("/", "__")
+              )
+            )
+          }
+          case _ =>
+            throw new IllegalArgumentException(s"Unrecognized URI scheme: 
$contextURI")
+        }
+
+        assert(
+          !Files.exists(flatPath),
+          s"File $flatPath already exists and would be overwritten, aborting!"
+        )
+        val bw = Files.newBufferedWriter(flatPath)
+        val fileAsString = new String(bytes, Charset.defaultCharset())
+
+        val references = referenceRegexes.flatMap { re =>
+          re.findAllIn(fileAsString).matchData.map(_.group(1))
+        }
+
+        val resolvedRefs = references.flatMap { ref =>
+          val optResolved = if (ref.startsWith("/")) {
+            // Dealing with an absolute path
+            Option(classLoader.findResource(ref.tail))
+          } else {
+            // Relative path
+            val refPath = contextPath.resolveSibling(ref).normalize()
+            if (Files.exists(refPath)) {
+              // Referenced path exists in either the same root resource
+              // diretory or jar file as the context schema
+              Some(refPath.toUri.toURL)
+            } else {
+              contextURI.getScheme match {
+                case "file" => {
+                  // Need to check other resource directories
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    Files.exists(Paths.get(root.toURI.resolve(ref)))
+                  )
+                  if (resolvedRoot.isDefined)
+                    Some(resolvedRoot.get.toURI.resolve(ref).toURL)
+                  else {
+                    // Maybe this path is actually absolute, just missing the
+                    // leading '/', check the classpath
+                    Option(classLoader.findResource(ref))
+                  }
+                }
+                case "jar" => {
+                  // Maybe this path in the JAR is actually absolute, just
+                  // missing the leading '/', try resolving it relative to the
+                  // root of the JAR
+                  val jarPath = contextPath.getRoot().resolve(ref)
+                  if (Files.exists(jarPath))
+                    Some(jarPath.toUri.toURL)
+                  else
+                    None

Review Comment:
   The deprecated fallback behavior is the same for file and jar schems, in 
both cases they should just look at the classpath as if the ref was intended to 
be absolute but was missing the leading slash. To avoid duplication, you might 
do something like this?
   
   ```scala
   val optResolvedRelative =  contextURI.getScheme match {
     case "file" => {
       // need to check other resourceDirectories since they would end up in 
the same same jar
       val resolvedRoot = ...
       resolvedRoot.map(_.toURI.resolve(ref).toULR)
     }
     case "jar" => {
       // nothing else to check, resolveSibling should have found it in the 
same jar
       None
    }
   }
   optResolvedRelative.orElse {
     // deprecated fallback, treat ref as if it were absolute just with the 
slash missing
     Option(classLoader.findResource(ref))
   }
   ```



##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +854,252 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+
+    /**
+     * Whether or not to publish the flattened schemas zip. Defaults to false.
+     *
+     * If projects want to publish flattened schemas then they must explicitly 
enable it by
+     * setting 'daffodilFlattenSchemas / publishArtifact := true'.
+     *
+     * If false, flattened schemas will not be created unless you explicitly 
run the
+     * daffodilFlattenSchemas.
+     */
+    daffodilFlattenSchemas / publishArtifact := false,
+
+    daffodilFlattenSchemas / artifact := Artifact(
+      name.value,
+      "flat",
+      "zip",
+      Some("flat"),
+      Vector(),
+      None
+    ),
+
+    /**
+     * Only grab the file types that need to be flattened. These files are only
+     * grabbed from the root project. Any referenced files will be pulled in
+     * from the classpath. Note that XML files are commonly used in XSLT for
+     * settings.
+     */
+    daffodilFlattenSchemas / includeFilter := "*.xsd" | "*.xsl" | "*.xslt" | 
"*.xml",
+    daffodilFlattenSchemas / excludeFilter := HiddenFileFilter,
+
+    daffodilFlattenResourceReferencePatterns := List(
+      """(?<!xsi:)schemaLocation=\"([^\"]*)\"""".r,
+      "href=\"([^\"]*)\"".r,
+      "document[(]'([^']*)'[)]".r
+    ),
+
+    daffodilFlattenSchemas / products := {
+
+      val logger = streams.value.log
+      val filter =
+        (daffodilFlattenSchemas / includeFilter).value -- 
(daffodilFlattenSchemas / excludeFilter).value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectURLs = (Compile / resourceDirectories).value.map { root =>
+        (root ** filter).get.map(_.toURI.toURL).toList
+      }.flatten
+
+      /**
+       * Create a URLClassLoader object with URLs to all resources used by the
+       * project. The class loader will be used to resolve references made
+       * within the flattened files.
+       */
+      val allClasspathURLs = (Test / 
externalDependencyClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val referenceRegexes = daffodilFlattenResourceReferencePatterns.value
+      val jarRegex = "(.*/(.*).jar)!(.*)".r
+
+      val seen = scala.collection.mutable.Set[URI]()
+      val unprocessed = scala.collection.mutable.Stack[URI]()
+      val unresolved = scala.collection.mutable.ArrayBuffer[(URI, String)]()
+      val jarFileSystems = scala.collection.mutable.Map[String, FileSystem]()
+      unprocessed.pushAll(projectURLs.map(_.toURI))
+      seen ++= projectURLs.map(_.toURI)
+      while (!unprocessed.isEmpty) {
+        val contextURI = unprocessed.pop
+        val bytes = contextURI.toURL.openStream().readAllBytes()
+
+        // Get a Java Path for the current file (whether it is in a JAR or
+        // just a regular file) and a Path for where the current file will be
+        // moved to in the flattened directory structure.  Note that using a
+        // Java Path on a regular file will use the default FileSystem while 
for
+        // JARs a FileSystem is created for interacting with the files inside
+        // the JAR. This allows all Path functions like resolveSibling/exists 
to
+        // work with both regular files or files contained inside a JAR.
+        val (contextPath, flatPath) = contextURI.getScheme match {
+          case "jar" =>
+            contextURI.toString match {
+              case jarRegex(jarPath, jarName, path) => {
+                val fs = jarFileSystems.getOrElseUpdate(
+                  Paths.get(jarPath).toString,
+                  FileSystems.newFileSystem(contextURI, new 
java.util.HashMap[String, Any]())
+                )
+                val cPath = fs.getPath(path)
+                (
+                  cPath,
+                  Paths.get(
+                    flatDir.toString,
+                    s"${jarName}__${cPath.toString.tail.replaceAll("/", "__")}"
+                  )
+                )
+              }
+              case _ =>
+                throw new IllegalArgumentException(s"Unable to parse JAR URI: 
$contextURI")
+            }
+          case "file" => {
+            val path = Paths.get(contextURI)
+            val root = (Compile / resourceDirectories).value
+              .find(dir => contextURI.getPath.startsWith(dir.toString))
+              .get
+              .toURI
+            (
+              path,
+              Paths.get(
+                flatDir.toString,
+                Paths.get(root).relativize(path).toString.replaceAll("/", "__")
+              )
+            )
+          }
+          case _ =>
+            throw new IllegalArgumentException(s"Unrecognized URI scheme: 
$contextURI")
+        }
+
+        assert(
+          !Files.exists(flatPath),
+          s"File $flatPath already exists and would be overwritten, aborting!"
+        )
+        val bw = Files.newBufferedWriter(flatPath)
+        val fileAsString = new String(bytes, Charset.defaultCharset())
+
+        val references = referenceRegexes.flatMap { re =>
+          re.findAllIn(fileAsString).matchData.map(_.group(1))
+        }
+
+        val resolvedRefs = references.flatMap { ref =>
+          val optResolved = if (ref.startsWith("/")) {
+            // Dealing with an absolute path
+            Option(classLoader.findResource(ref.tail))
+          } else {
+            // Relative path
+            val refPath = contextPath.resolveSibling(ref).normalize()
+            if (Files.exists(refPath)) {
+              // Referenced path exists in either the same root resource
+              // diretory or jar file as the context schema
+              Some(refPath.toUri.toURL)
+            } else {
+              contextURI.getScheme match {
+                case "file" => {
+                  // Need to check other resource directories
+                  val resolvedRoot = (Compile / 
resourceDirectories).value.find(root =>
+                    Files.exists(Paths.get(root.toURI.resolve(ref)))
+                  )
+                  if (resolvedRoot.isDefined)
+                    Some(resolvedRoot.get.toURI.resolve(ref).toURL)
+                  else {
+                    // Maybe this path is actually absolute, just missing the
+                    // leading '/', check the classpath
+                    Option(classLoader.findResource(ref))
+                  }
+                }
+                case "jar" => {
+                  // Maybe this path in the JAR is actually absolute, just
+                  // missing the leading '/', try resolving it relative to the
+                  // root of the JAR
+                  val jarPath = contextPath.getRoot().resolve(ref)
+                  if (Files.exists(jarPath))
+                    Some(jarPath.toUri.toURL)
+                  else
+                    None
+                }
+                case _ => None
+              }
+            }
+          }
+          if (optResolved.isEmpty)
+            unresolved.append((contextURI, ref))
+          optResolved.map(resolved => ref -> resolved)
+        }.toMap
+
+        val updatedFileAsString = {
+          resolvedRefs.foldLeft(fileAsString) {
+            case (input, (ref, optResolvedURL)) => {
+              // For each reference replace each instance of it in the
+              // file with the same reference but with "/" changed to "__"
+              val resolvedURI = optResolvedURL.toURI
+              val relativized = resolvedURI.getScheme match {
+                case "jar" =>
+                  resolvedURI.toString match {
+                    case jarRegex(_, jarName, path) =>
+                      s"${jarName}__${path.tail.replaceAll("/", "__")}"

Review Comment:
   You do the replaceAll("/", "__") below. Maybe this wants to be
    ```
   s"${jarName}/${path.tail}"
   ```
   So kindof the implication is the `relatived` variable always looks like a 
path (and jars just prepend another directory), and later logic actually 
flattens that path to replace slashes with underscores? My thinking is if we 
ever want to do something instead of double underscores it'll only be in one 
place below.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to