You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ar...@apache.org on 2022/08/05 17:38:25 UTC

[daffodil-vscode] 21/28: Add TDML wrapper that will simplify the actual TDML interface Remove temporary/test code Add in first attempt at support for append/execute

This is an automated email from the ASF dual-hosted git repository.

arosien pushed a commit to branch daffodil-vscode-tdml
in repository https://gitbox.apache.org/repos/asf/daffodil-vscode.git

commit 14902500abeb7433bf19d0b236400156aff0f68b
Author: Michael Hoke <mi...@nteligen.com>
AuthorDate: Mon Jul 25 18:49:07 2022 -0400

    Add TDML wrapper that will simplify the actual TDML interface
    Remove temporary/test code
    Add in first attempt at support for append/execute
---
 .../org.apache.daffodil.debugger.dap/Parse.scala   | 57 ++++++++--------
 .../main/scala/org.apache.daffodil.tdml/TDML.scala | 72 +++++++-------------
 .../org.apache.daffodil.tdml/TDMLWrapper.scala     | 76 ++++++++++++++++++++++
 3 files changed, 130 insertions(+), 75 deletions(-)

diff --git a/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala b/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
index b55def1..1a390ed 100644
--- a/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
+++ b/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
@@ -46,8 +46,8 @@ import org.apache.daffodil.util.Misc
 import org.typelevel.log4cats.Logger
 import org.typelevel.log4cats.slf4j.Slf4jLogger
 import scala.util.Try
-// import org.apache.commons.io.output.NullOutputStream
-import org.apache.daffodil.tdml.TDML
+import org.apache.commons.io.output.NullOutputStream
+import org.apache.daffodil.tdml.TDMLWrapper
 
 trait Parse {
 
@@ -282,6 +282,22 @@ object Parse {
       infoset <- Resource.eval(Queue.bounded[IO, Option[String]](10)) // TODO: it's a bit incongruous to have a separate channel for infoset changes, vs. streaming Parse.Event values
       control <- Resource.eval(Control.stopped())
 
+      pathTuple = args.tdmlConfig match {
+        case Debugee.LaunchArgs.TDMLConfig.Config(action, name, description, tdmlPath) =>
+          if (action == "execute") {
+            TDMLWrapper.execute(args.schemaPath, args.dataPath, name, description, tdmlPath)
+          }
+          else
+            (args.schemaPath, args.dataPath)
+        case _ =>
+          (args.schemaPath, args.dataPath)
+      }
+
+      // pathTuple will start with the original values of these paths and will only get updated if
+      // these paths need to be changed.
+      args.schemaPath = pathTuple._1
+      args.dataPath = pathTuple._2
+
       latestData <- Stream.fromQueueNoneTerminated(data).holdResource(DAPodil.Data.empty)
 
       latestInfoset <- Resource.eval(SignallingRef[IO, String](""))
@@ -335,6 +351,7 @@ object Parse {
         breakpoints,
         control
       )
+
       startup = dapEvents.offer(Some(ConfigEvent(args))) *>
         (if (args.stopOnEntry)
            control.step() *> state.offer(
@@ -359,35 +376,21 @@ object Parse {
                   if (action == "generate")
                     args.infosetOutput match {
                       case Debugee.LaunchArgs.InfosetOutput.File(path) =>
-                        // Logger[IO].debug("Getting ready to generate")
-                        IO(TDML.generate(path.toString(), args.dataPath.toString(), args.schemaPath.toString(), name, description, tdmlPath))
+                        IO(TDMLWrapper.generate(path, args.dataPath, args.schemaPath, name, description, tdmlPath))
                       case _ =>
-                        Logger[IO].debug("Non-file InfosetOutput")
-                        // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
+                        IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
+                    }
+                  else if (action == "append")
+                    args.infosetOutput match {
+                      case Debugee.LaunchArgs.InfosetOutput.File(path) =>
+                        IO(TDMLWrapper.append(path, args.dataPath, args.schemaPath, name, description, tdmlPath))
+                      case _ =>
+                        IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
                     }
                   else
-                    Logger[IO].debug("TDMLConfig is not generate")
-                    // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))  
-                case _ =>
-                  Logger[IO].debug("Not sure why this is here")
-                  // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
-              /* args.infosetOutput match {
-                case Debugee.LaunchArgs.InfosetOutput.File(path) =>
-                  args.tdmlConfig match {
-                    // case Debugee.LaunchArgs.TDMLConfig.Config(action, name, description, tdmlPath) =>
-                      if (action == "generate")
-                        Logger[IO].debug("Makes it into the generate")
-                        // TDML.generate(path.toString(), args.dataPath.toString(), args.schemaPath.toString(), name, description, tdmlPath)
-                      else
-                        Logger[IO].debug("TDMLConfig is None")
-                        // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))  
-                    case _ =>
-                      Logger[IO].debug("Not sure why this is here")
-                      // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
-                  }
+                    IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))  
                 case _ =>
-                  Logger[IO].debug("Non-file InfosetOutput")
-                  // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)) */
+                  IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
               }
             ),
             infosetChanges
diff --git a/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala b/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
index e388666..4bc88e1 100644
--- a/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
+++ b/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
@@ -1,17 +1,15 @@
 package org.apache.daffodil.tdml
 
-import javax.xml.bind.JAXBContext
-import java.io.FileOutputStream
-import cats.effect.IO
 import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Paths
+import javax.xml.bind.JAXBContext
+import javax.xml.bind.JAXBElement
 import javax.xml.bind.Marshaller
+import javax.xml.namespace.QName
+import javax.xml.bind.annotation.XmlType
 
-// import org.typelevel.log4cats.Logger
-// import org.typelevel.log4cats.slf4j.Slf4jLogger
-
-// TODO: Put TDML path in class definition?
 object TDML {
-  // implicit val logger: Logger[IO] = Slf4jLogger.getLogger
   // Create a ParserTestCaseType object that can be put into a TestSuite
   // These types are generated when JAXB is executed on the TDML schema
   // 
@@ -47,9 +45,17 @@ object TDML {
     docPart.setType(DocumentPartTypeEnum.FILE)
     docPart.setValue(dataPath)
 
+    // These lines are necessary because there is no @XmlRootElement annotation on the DocumentPartType class in JAXB
+    // Ideally, we would want to have JAXB add the annotation - probably with the bindings.xjb file. The only way I found
+    //   that did that required an external plugin just to add the annotation (https://github.com/highsource/jaxb2-annotate-plugin).
+    // We are getting the namespace from the JAXB class so that we don't have to hard-code it here
+    // Unfortunately, it seems like hard-coding the class name isn't an easy thing to avoid. There is a name in the XmlType
+    //   annotation, but it is documentPartType instead of documentPart. We would need to remove the Type from this anyway.
+    val tdmlNamespacePrefix = classOf[DocumentPartType].getAnnotation(classOf[XmlType]).namespace()
+    val docPartElement = new JAXBElement[DocumentPartType](new QName(tdmlNamespacePrefix, "documentPart"), classOf[DocumentPartType], docPart)
+
     val doc = factory.createDocumentType()
-    // The following line causes the output of the marshalling to be empty
-    doc.getContent().add(docPart)
+    doc.getContent().add(docPartElement)
 
     val testCase = factory.createParserTestCaseType()
     testCase.setName(tdmlName)
@@ -74,10 +80,8 @@ object TDML {
   // tdmlDescription: Description for the DFDL operation
   // tdmlPath:        Path to the TDML file
   // 
-  // There is a suiteName attribute in the root element of the document. This is set to $tdmlName
-  // TODO: I think the return type here should just be Unit
+  // There is a suiteName attribute in the root element of the document. This is set to tdmlName
   def generate(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
-    // Logger[IO].debug("Generating")
     val factory = new ObjectFactory()
 
     val testSuite = factory.createTestSuite()
@@ -85,12 +89,6 @@ object TDML {
     testSuite.setDefaultRoundTrip(RoundTripType.ONE_PASS)
     testSuite.getTutorialOrParserTestCaseOrDefineSchema().add(createTestCase(infosetPath, dataPath, schemaPath, tdmlName, tdmlDescription))
 
-    // Logger[IO].debug("Getting ready to send XML to file")
-    // val marshaller = JAXBContext.newInstance(classOf[TestSuite]).createMarshaller()
-    // marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
-    // marshaller.marshal(testSuite, new FileOutputStream(tdmlPath))
-    // JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, fos)
-    // val fos = new FileOutputStream(tdmlPath)
     val marshaller = JAXBContext.newInstance(classOf[TestSuite]).createMarshaller()
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
     marshaller.marshal(testSuite, new java.io.File(tdmlPath))
@@ -105,15 +103,13 @@ object TDML {
   // tdmlName:        Name of the DFDL operation
   // tdmlDescription: Description for the DFDL operation
   // tdmlPath:        Path to the TDML file
-  // 
-  // TODO: I think the return type here should just be Unit
-  def append(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): IO[Unit] = {
+  def append(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
 
     val testSuite = JAXBContext.newInstance(classOf[TestSuite]).createUnmarshaller().unmarshal(new File(tdmlPath)).asInstanceOf[TestSuite]
 
     testSuite.getTutorialOrParserTestCaseOrDefineSchema().add(createTestCase(infosetPath, dataPath, schemaPath, tdmlName, tdmlDescription))
 
-    IO(JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, new FileOutputStream(tdmlPath)))
+    JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, new FileOutputStream(tdmlPath))
   }
 
   // Find the parameters needed to execute a DFDL parse based on the given TDML Parameters
@@ -128,41 +124,21 @@ object TDML {
     val testCaseList = JAXBContext.newInstance(classOf[TestSuite]).createUnmarshaller().unmarshal(new File(tdmlPath)).asInstanceOf[TestSuite].getTutorialOrParserTestCaseOrDefineSchema()
 
     testCaseList.forEach { tc =>
-      // var foundDoc = ""
-      // var foundInfoset = ""
-
-      // TODO: Do I really have to cast to instances every time? I've already checked that they are...
       tc match {
         case ptc: ParserTestCaseType =>
           if (ptc.getName() == tdmlName && ptc.getDescription() == tdmlDescription) {
             ptc.getTutorialOrDocumentOrInfoset().forEach { dis =>
               dis match {
                 case doc: DocumentType =>
-                  return (ptc.getModel(), doc.getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue())
+                  // The right part of the tuple only takes the first DocumentPart inside the Document.
+                  // In the case that there are more than one, any extras will be ignored.
+                  val schemaPath = Paths.get(ptc.getModel()).toFile().getCanonicalPath()
+                  val dataPath = Paths.get(doc.getContent().get(0).asInstanceOf[JAXBElement[DocumentPartType]].getValue().getValue()).toFile().getCanonicalPath()
+                  return (schemaPath, dataPath)
               }
             }
           }
       }
-      /*if (tc.isInstanceOf[ParserTestCaseType]) {
-        // Match name and description of potential test case
-        if (tc.asInstanceOf[ParserTestCaseType].getName() == tdmlName && tc.asInstanceOf[ParserTestCaseType].getDescription() == tdmlDescription) {
-          tc.asInstanceOf[ParserTestCaseType].getTutorialOrDocumentOrInfoset().forEach { dis =>
-            if (dis.isInstanceOf[DocumentType]) {
-              // foundDoc = dis.asInstanceOf[DocumentType].getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue()
-              // if (!foundInfoset.isEmpty()) {
-                // return (tc.asInstanceOf[ParserTestCaseType].getModel(), foundInfoset, foundDoc)
-              // }
-              return (tc.asInstanceOf[ParserTestCaseType].getModel(), dis.asInstanceOf[DocumentType].getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue())
-            }
-            // else if (dis.isInstanceOf[InfosetType]) {
-              // foundInfoset = dis.asInstanceOf[InfosetType].getDfdlInfoset().getContent().indexOf(0).asInstanceOf[String]
-              // if (!foundDoc.isEmpty()) {
-                // return (tc.asInstanceOf[ParserTestCaseType].getModel(), foundInfoset, foundDoc)
-              // }
-            // }
-          }
-        }
-      }*/
     }
 
     // If there is no test case in the TDML file meeting the name/description criteria, return empty
diff --git a/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala b/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
new file mode 100644
index 0000000..33df6dc
--- /dev/null
+++ b/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
@@ -0,0 +1,76 @@
+package org.apache.daffodil.tdml
+
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+
+object TDMLWrapper {
+  // Convert an absolute path into a path relative to the current working directory
+  // 
+  // path: Absolute path to convert into a relative path
+  // 
+  // Returns the relative path. Note that this path is given as a string.
+  private def convertToRelativePath(path: Path): String = {
+    // Don't forget the getParent because toAbsolutePath returns something like the following:
+    //   /absolute/path/to/directory/.
+    // The getParent gets rid of the dot at the end.
+    var workingDir = Paths.get(".").toAbsolutePath().getParent()
+    var prefix = ""
+
+    // This is used to back up the path tree in order to find the first common ancestor of both paths
+    // If a user wants to use a file not in or under the current working directory, this will be required to
+    //   produce the expected output.
+    // A possible use case of this is where a user has a data folder and a schema folder that are siblings.
+    while (!path.startsWith(workingDir) && Paths.get(workingDir.toString()).getParent() != null)
+    {
+      workingDir = Paths.get(workingDir.toString()).getParent()
+      // Need to add the dots to represent that we've gone back a step up the path
+      prefix += ".." + File.separator
+    }
+
+    return prefix + new File(workingDir.toString()).toURI().relativize(new File(path.toString()).toURI()).getPath().toString()
+  }
+
+  // Generate a new TDML file.
+  // Paths given to this function should be absolute as they will be converted to relative paths
+  //
+  // infosetPath:     Path to the infoset
+  // dataPath:        Path to the data file
+  // schemaPath:      Path to the DFDL Schema
+  // tdmlName:        Name of the DFDL operation
+  // tdmlDescription: Description for the DFDL operation
+  // tdmlPath:        Path to the TDML file
+  def generate(infosetPath: Path, dataPath: Path, schemaPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
+    TDML.generate(convertToRelativePath(infosetPath), convertToRelativePath(dataPath), convertToRelativePath(schemaPath), tdmlName, tdmlDescription, tdmlPath)
+  }
+
+  // Append a new test case to an existing TDML file.
+  // Paths given to this function should be absolute as they will be converted to relative paths
+  // 
+  // infosetPath:     Path to the infoset
+  // dataPath:        Path to the data file
+  // schemaPath:      Path to the DFDL Schema
+  // tdmlName:        Name of the DFDL operation
+  // tdmlDescription: Description for the DFDL operation
+  // tdmlPath:        Path to the TDML file
+  def append(infosetPath: Path, dataPath: Path, schemaPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
+    TDML.append(convertToRelativePath(infosetPath), convertToRelativePath(dataPath), convertToRelativePath(schemaPath), tdmlName, tdmlDescription, tdmlPath)
+  }
+
+  // Find the parameters needed to execute a DFDL parse based on the given TDML Parameters
+  // 
+  // tdmlName:        Test case name to run
+  // tdmlDescription: Description of test case to run
+  // tdmlPath:        File path of TDML file to extract test case from
+  // 
+  // Returns a tuple containing the following (Path to DFDL Schema, Path to Data File)
+  // All paths returned could be either relative or absolute - it depends on what exists in the TDML file
+  def execute(schemaPath: Path, dataPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): (Path, Path) = {
+    val (newSchemaPath, newDataPath) = TDML.execute(tdmlName, tdmlDescription, tdmlPath)
+    if (newSchemaPath.length > 0 && newDataPath.length > 0) {
+      return (Paths.get(newSchemaPath), Paths.get(newDataPath))
+    }
+
+    return (schemaPath, dataPath)
+  }
+}