Skip to content

Commit

Permalink
Refactor NodeInfo.PrimType. Add DFDLPrimType Java enum.
Browse files Browse the repository at this point in the history
Bunch of cleanups of NodeInfo dead or little-used code.

optPrimType is now available on all TypeNodes and associated
Kind Scala types. (eliminates list searching to find primType)

Contributes to, but does not complete work on layers API.

DAFFODIL-2845
  • Loading branch information
mbeckerle committed Feb 14, 2024
1 parent da3cc9f commit ff9ba41
Show file tree
Hide file tree
Showing 16 changed files with 358 additions and 319 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@

package org.apache.daffodil.core.dsom

import java.lang.{ Boolean => JBoolean, Long => JLong }
import java.lang.{ Boolean => JBoolean }
import java.lang.{ Long => JLong }
import scala.xml.NamespaceBinding

import org.apache.daffodil.core.dpath._
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.schema.annotation.props.Found
import org.apache.daffodil.lib.util.DPathUtil
import org.apache.daffodil.lib.xml.NamedQName
import org.apache.daffodil.runtime1.BasicComponent
import org.apache.daffodil.runtime1.dpath.NodeInfo
import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
import org.apache.daffodil.runtime1.dpath.NodeInfo.AnyAtomic
import org.apache.daffodil.runtime1.dsom._
import org.apache.daffodil.runtime1.infoset.DataValue.DataValuePrimitive

Expand Down Expand Up @@ -251,33 +253,39 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
// required. If something is passed in that does not convert to the
// expected type it will result in error.

// must be able to convert this to a primitive type
val maybePrimType = PrimType.fromNodeInfo(nodeInfoKind)
if (maybePrimType.isEmpty) {
val msg = "No known primitive type to convert logical value to: %s"
compileInfoWhereExpressionWasLocated.SDE(msg, nodeInfoKind)
}
nodeInfoKind match {
case atomic: AnyAtomic.Kind => {

// remove the leading escape curly brace if it exists
val literal =
if (exprOrLiteral.startsWith("{{")) exprOrLiteral.tail
else exprOrLiteral
// remove the leading escape curly brace if it exists
val literal =
if (exprOrLiteral.startsWith("{{")) exprOrLiteral.tail
else exprOrLiteral

val logical: DataValuePrimitive =
try {
maybePrimType.get.fromXMLString(literal)
} catch {
case e: Exception => {
val msg = "Unable to convert logical value \"%s\" to %s: %s"
compileInfoWhereExpressionWasLocated.SDE(
msg,
exprOrLiteral,
nodeInfoKind,
e.getMessage,
)
}
}
val logical: DataValuePrimitive =
try {
atomic.primType.fromXMLString(literal)
} catch {
case e: Exception => {
val msg = "Unable to convert logical value \"%s\" to %s: %s"
compileInfoWhereExpressionWasLocated.SDE(
msg,
exprOrLiteral,
nodeInfoKind,
e.getMessage,
)
}
}

new ConstantExpression[T](qn, nodeInfoKind, logical.getAnyRef.asInstanceOf[T])
new ConstantExpression[T](qn, nodeInfoKind, logical.getAnyRef.asInstanceOf[T])
}
// $COVERAGE-OFF$
case _ => {
val msg = "No known primitive type to convert logical value to: %s"
Assert.invariantFailed(
msg + compileInfoWhereExpressionWasLocated.schemaFileLocation.toString,
)
}
// $COVERAGE-ON$
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -884,11 +884,6 @@ trait LayeringRuntimeValuedPropertiesMixin extends RawLayeringRuntimeValuedPrope
)
}

// final lazy val layerBoundaryMarkEv = {
// if (maybeLayerBoundaryMarkEv.isEmpty) layerBoundaryMarkRaw // must be defined
// maybeLayerBoundaryMarkEv.get
// }

final lazy val maybeLayerBoundaryMarkEv = {
if (optionLayerBoundaryMarkRaw.isDefined) {
val ev = new LayerBoundaryMarkEv(layerBoundaryMarkExpr, tci)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ sealed trait SimpleTypeBase extends TypeBase with SimpleTypeView {

/**
* PrimType nodes are part of the runtime. For compilation, we need a notion
* of primitive type that derives from the same base a SimpleTypeBase and
* of primitive type that derives from the same base as SimpleTypeBase and
* ComplexTypeBase, and it needs to have methods that take and return
* compiler-only object types; hence we can't define a base in the runtime
* because it can't have those methods; hence, can't achieve the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ExplicitLengthLimitingStream(in: InputStream, limit: Long) extends FilterI

private var numRemaining = limit

override def read(buf: Array[Byte], off: Int, len: Int) = {
override def read(buf: Array[Byte], off: Int, len: Int): Int = {
Assert.invariant(numRemaining >= 0)
if (numRemaining == 0) -1
else if (len == 0) 0
Expand Down Expand Up @@ -97,7 +97,7 @@ object BoundaryMarkLimitingStream {
boundaryMark: String,
charset: Charset,
targetChunkSize: Int = 32 * 1024,
) = {
): InputStream = {

Assert.usage(targetChunkSize >= 1)
Assert.usage(boundaryMark.length >= 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.apache.daffodil.lib.exceptions.Assert
/**
* Delay[T]
*
* Used to create cyclic objects without side-effects.
*
* This Delayed evaluation technique is an alternative to staggered
* multi-stage factories, or currying. Turns out currying works fine in
* Scala for functions, but not for class constructors, so it's easier to
Expand Down Expand Up @@ -65,7 +67,7 @@ import org.apache.daffodil.lib.exceptions.Assert
*
* }}}
*/
final class Delay[T] private (private var box: Delay.Box[T], sym: Symbol)
final class Delay[T] private (private var box: Delay.Box[T], ctxt1: AnyRef)
extends PreSerialization {
//
// This trick of taking another object on a var, and
Expand Down Expand Up @@ -97,20 +99,23 @@ final class Delay[T] private (private var box: Delay.Box[T], sym: Symbol)
/**
* Create a string representation. Does not force the value to be computed.
*/
override def toString = {
val bodyString = if (hasValue) ", " + value.toString else ""
val objString =
if (hasValue) ""
else {
box
override def toString: String = {
val str =
if (hasValue) {
if (ctxt1 ne null)
s"Delay(${Delay.nameOrValue(ctxt1)}, $value)"
else
s"Delay($value)"
} else {
s"Delay($box)"
}
"Delay(" + sym + ", " + objString + bodyString + ")"
str
}

override def preSerialization = {
if (!hasValue) {
Assert.invariant(box ne null)
val msg = s"No value for delay. Containing object not initialized? ID Symbol:$sym " +
val msg = s"No value for delay. Containing object not initialized? ID Symbol:$ctxt1 " +
s"object ${box}"
Assert.invariantFailed(msg)
}
Expand All @@ -121,32 +126,71 @@ final class Delay[T] private (private var box: Delay.Box[T], sym: Symbol)
final private def writeObject(out: java.io.ObjectOutputStream): Unit = serializeObject(out)
}

/**
* Factory for Delay[T] objects. Used to create cyclic structures without using side-effects.
*/
object Delay {

/**
* Create a delayed expression object.
*
* @param ctxt1 Used for debugging only. ctxt1.toString is used in display of these object.
* Can be null if not needed.
* @param ctxt2 Used for debugging only. ctxt2.toString is used in display of these objects.
* Can be null if not needed.
* @param delayedExpression an argument expression which will not be evaluated until required
* @tparam T type of the argument. (Usually inferred by Scala.)
* @return the Delay object
*/
def apply[T](sym: Symbol, obj: AnyRef, delayedExpression: => T) = {
new Delay(new Box(sym, obj, delayedExpression), sym)
def apply[T](ctxt1: AnyRef, ctxt2: AnyRef, delayedExpression: => T): Delay[T] = {
new Delay(new Box(ctxt1, ctxt2, delayedExpression), ctxt1)
}

/**
* Specifically, this is NOT serializable.
* Two-arg version to create a delayed expression object.
* @param ctxt1 Used for debugging only. ctxt1.toString is used in display of these object.
* Can be null if not needed.
* @param delayedExpression an argument expression which will not be evaluated until required
* @tparam T type of the argument. (Usually inferred by Scala.)
* @return the Delay object.
*/
def apply[T](ctxt1: AnyRef, delayedExpression: => T): Delay[T] =
new Delay(new Box(ctxt1, null, delayedExpression), ctxt1)

private def nameOrValue(x: AnyRef) = x match {
case d: NamedMixinBase => d.diagnosticDebugName
case _ => x
}

/**
* Box object to hold delayed expression.
*
* Needed so that we can look at Delay objects in the debugger and
* printed output without forcing the evaluation of their delayed contents.
*
* Also potentially helps with discarding everything Scala needs
* to implement Delays, once they have been forced.
*
* Note: this is NOT serializable, on purpose.
* Serialization must force all Delay objects.
* @param ctxt1 Used for debugging only. ctxt1.toString is used in display of these object.
* Can be null if not needed.
* @param ctxt2 Used for debugging only. ctxt2.toString is used in display of these objects.
* Can be null if not needed.
* @param delayedExpression an argument expression which will not be evaluated until required
* @tparam T
*/
private class Box[T](val sym: Symbol, val obj: AnyRef, delayedExpression: => T) {
private class Box[T](val ctxt1: AnyRef, val ctxt2: AnyRef, delayedExpression: => T) {
lazy val value = delayedExpression

override def toString() = {
val desc = obj match {
case d: NamedMixinBase => "(" + d.diagnosticDebugName + ")"
case _ => ""
}
"box(" + obj.hashCode() + desc + ")"
private lazy val str = {
val ctxtList = Seq(Option(ctxt1), Option(ctxt2)).flatten.distinct
val descList: Seq[_] = ctxtList.map { nameOrValue(_) }
val desc =
if (descList.isEmpty) "@" + hashCode().toString
else descList.mkString("(", ", ", ")")
s"box$desc"
}
override def toString() = str
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.daffodil.lib.util

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class TestDelay {
object Context1 extends NamedMixinBase
object Context2 extends NamedMixinBase

@Test def testDelayToString1(): Unit = {
val d = Delay("context1", "context2", 1 + 2)
val ds = d.toString
val v = d.value
assertEquals("3", v.toString)
val dsv = d.toString
assertEquals("Delay(box(context1, context2))", ds)
assertEquals("Delay(context1, 3)", dsv)
}

@Test def testDelayToString2(): Unit = {
val d = Delay(Context1, Context2, 1 + 2)
val ds = d.toString
val v = d.value
assertEquals("3", v.toString)
val dsv = d.toString
assertEquals("Delay(box(Context1, Context2))", ds)
assertEquals("Delay(Context1, 3)", dsv)
}

@Test def testDelayToString3(): Unit = {
val d = Delay(Context1, 1 + 2)
val ds = d.toString
val v = d.value
assertEquals("3", v.toString)
val dsv = d.toString
assertEquals("Delay(box(Context1))", ds)
assertEquals("Delay(Context1, 3)", dsv)
}

@Test def testDelayToString4(): Unit = {
val d = Delay(null, Context2, 1 + 2)
val ds = d.toString
val v = d.value
assertEquals("3", v.toString)
val dsv = d.toString
assertEquals("Delay(box(Context2))", ds)
assertEquals("Delay(3)", dsv)
}

@Test def testDelayToString5(): Unit = {
val d = Delay(null, null, 1 + 2)
val ds = d.toString
val v = d.value
assertEquals("3", v.toString)
val dsv = d.toString
assertTrue(ds.startsWith("Delay(box@"))
assertTrue(ds.endsWith(")"))
assertEquals("Delay(3)", dsv)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.daffodil.runtime1.api;

package org.apache.daffodil.runtime1.dpath

import org.junit.Assert.assertTrue
import org.junit.Test

class TestNodeInfo {

@Test def test_nodeInfoCanBeConstructed(): Unit = {
assertTrue(NodeInfo.isXDerivedFromY("Byte", "Long"))
}
/**
* An enumeration of all DFDL's simple types.
* <p/>
* Intended to be easily used from Java code calling Daffodil APIs.
*/
public enum DFDLPrimType {

String,
Int,
Byte,
Short,
Long,
Integer,
Decimal,
UnsignedInt,
UnsignedByte,
UnsignedShort,
UnsignedLong,
NonNegativeInteger,
Double,
Float,
HexBinary,
AnyURI,
Boolean,
DateTime,
Date,
Time
}
Loading

0 comments on commit ff9ba41

Please sign in to comment.