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


##########
src/main/scala/org/apache/daffodil/DaffodilPlugin.scala:
##########
@@ -840,6 +858,198 @@ object DaffodilPlugin extends AutoPlugin {
     }
   }
 
+  def daffodilFlattenSettings: Seq[Setting[_]] = Seq(
+    daffodilFlattenTarget := target.value / 
s"${name.value}-${version.value}-flat.zip",
+    /* Paths in daffodilFlattenExcludes/Includes that are not globbed at the 
start are
+     * generally only going to match paths within JAR files on the classpath.
+     * In order to deal with paths on the filesystem we need to glob the start
+     * of the path to account for different directory structure before the
+     * schema project path.
+     */
+    daffodilFlattenIncludes := "*.xsd" | "*.xsl" | "*.xslt" | "*.xml",
+    daffodilFlattenExcludes := HiddenFileFilter,
+
+    /**
+     * 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
+    ),
+    daffodilFlattenSchemas := {
+
+      val logger = streams.value.log
+      val filter = daffodilFlattenIncludes.value -- 
daffodilFlattenExcludes.value
+
+      val flatDir = target.value / "flatDir"
+      if (flatDir.exists())
+        IO.delete(flatDir)
+      IO.createDirectory(flatDir)
+
+      val projectResources = (Compile / resourceDirectories).value
+        .map { root => root.toURI.toURL -> (root ** 
filter).get.map(_.toURI.toURL).toList }
+        .toMap
+
+      /* Get all dependency jars and resources specific to this project. Note
+       * that the "Test" configuration is used for JAR files in order to ensure
+       * we pull in XSD files from daffodil-lib, as in many schema projects the
+       * daffodil dependencies are only used for testing, not compiling. Also
+       * note that we want to use Test/externalDependencyClasspath to get
+       * dependency jars, and not something like Test/fullClasspath or
+       * Test/dependencyClasspath, since those could trigger expensive resource
+       * generators or compilation of internal test jars that we don't need--we
+       * only need Compile/resources from this project and Test/dependency jars
+       * from external projects
+       */
+      val projectURLs = (Compile / 
resourceDirectories).value.map(_.toURI.toURL)
+      val allClasspathURLs = (Compile / 
fullClasspath).value.map(_.data.toURI.toURL)
+      val classLoader = new URLClassLoader((projectURLs ++ 
allClasspathURLs).toArray, null)
+
+      val schemaLocationPattern = """schemaLocation=\"([^\"]*)\"""".r
+      val hrefPattern = "href=\"([^\"]*)\"".r
+      val documentPattern = "document[(]'([^']*)'[)]".r
+      val referenceRegexes = List(schemaLocationPattern, hrefPattern, 
documentPattern)
+
+      val referencedFiles = {
+        // This annotation simply warns if the compiler cannot enable tail 
call optimization
+        @scala.annotation.tailrec
+        def getReferences(root: URL, parents: Seq[URL], acc: 
ArrayBuffer[URL]): Seq[URL] = {
+          val rootPath = Paths.get(root.toURI)
+          parents match {
+            // Already processed this file (h), proceed with rest of files (t)
+            case h :: t if (acc.contains(h)) =>
+              getReferences(
+                root,
+                t,
+                acc
+              )
+            // Need to process this file (h)
+            case h :: t => {
+              val relPath = h.toString match {
+                case j if (j.startsWith("jar:")) => 
Paths.get(j.toString.split("!")(1).tail)
+                case f if (f.startsWith("file:")) => 
rootPath.relativize(Paths.get(h.toURI))
+              }
+              val bytes = h.openStream().readAllBytes()
+              val newPath = Paths.get(
+                flatDir.toString,
+                relPath.toString.replaceAll("/", "__")
+              )
+              val bw = Files.newBufferedWriter(newPath)
+              val fileAsString = new String(bytes, Charset.defaultCharset())
+              val references = {
+                referenceRegexes.flatMap { re =>
+                  re.findAllIn(fileAsString).matchData.map(_.group(1))
+                }
+              }
+
+              // Resolve the location of all references on the actual file
+              // system
+              val resolvedReferences = references
+                .map { ref =>
+                  val origLocation = {
+                    if (ref.contains("urn:")) {
+                      val urn = ref.split(" ")(1)
+                      if (urn.startsWith("/"))
+                        urn.tail
+                      else
+                        urn
+                    } else if (ref.startsWith("/"))
+                      ref.tail
+                    else
+                      ref
+                  }
+                  val res = {
+                    val r = classLoader.findResource(origLocation)
+                    if (r == null) {
+                      val rrel = List(relPath.getParent, 
origLocation).mkString("/")
+                      classLoader.findResource(rrel)

Review Comment:
   This is the code Daffodil uses to look up relative `schemaLocation` 
attributes:
   
   
https://github.com/apache/daffodil/blob/main/daffodil-core/src/main/scala/org/apache/daffodil/lib/xml/XMLUtils.scala#L1553-L1564
   
   That uses `Misc.getResourceRelativeOnlyOption`
   
   Which is here:
   
   
https://github.com/apache/daffodil/blob/main/daffodil-core/src/main/scala/org/apache/daffodil/lib/util/Misc.scala#L141-L155
   
   That use the `optRelative[Jar,File]Option functions`, which do not use the 
classpath. Dependency order does not have affect when resolving relative 
schemaLocations (except the deprecated behavior of resolving relative paths as 
if they were just missing a leading slash to make them absolute).
   
   So using the classpath to lookup relative schemaLocations does not match 
Daffodil's behavior. In most cases it will work the same since rarely do 
different jars have the same paths, and maybe reversing happens to get the 
right result in some cases, but I think there are edge cases where it will not 
have the same behavior of Daffodil, which means the flattened schemas might not 
be the same as Daffodil.
   
   Note that in the jreap/link16 case, all the schemaLocations are absolute so 
dependency order does matter because every schemaLocation is looked up on the 
classpath, and probably why reversing was required in the previous 
implementation. So the jreap/link16 project just needs to be very careful that 
any common files that appear in both jars are actually same, since whatever is 
first on the classpath will be used if they appear in both jars.



-- 
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