diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala index 8e39d7db43..b9e05b3d94 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala @@ -111,7 +111,7 @@ final class InputSourceDataInputStream private (val inputSource: InputSource) } override final val cst: MarkState = new MarkState - val markStack = new MStackOf[MarkState] + val markStack = new MStackOf[MarkState](16) val markPool = new MarkPool() /** diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/lib/util/MStack.scala b/daffodil-core/src/main/scala/org/apache/daffodil/lib/util/MStack.scala index 6c25a867bb..6beea37385 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/lib/util/MStack.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/lib/util/MStack.scala @@ -24,6 +24,12 @@ import Maybe.* object MStack { final case class Mark(val v: Int) extends AnyVal val nullMark = Mark(0) + + /** + * Off by default: growing past initialSize isn't itself wrong, so paying + * this bookkeeping cost on every push isn't worth it normally. + */ + final inline val trackMaxSizeReached = false } /** @@ -33,35 +39,25 @@ object MStack { * catches improper initialization. These were not initializing properly, * so the idiom evolved to use the scala initializers. */ -final class MStackOfBoolean private () - extends MStack[Boolean]((n: Int) => new Array[Boolean](n), false) +final class MStackOfBoolean private (initialSize: Int) + extends MStack[Boolean]((n: Int) => new Array[Boolean](n), false, initialSize) object MStackOfBoolean { - def apply() = { - val stk = new MStackOfBoolean() - stk.init() - stk - } + def apply(initialSize: Int = 32) = new MStackOfBoolean(initialSize) } -final class MStackOfInt extends MStack[Int]((n: Int) => new Array[Int](n), 0) +final class MStackOfInt(initialSize: Int) + extends MStack[Int]((n: Int) => new Array[Int](n), 0, initialSize) object MStackOfInt { - def apply() = { - val stk = new MStackOfInt() - stk.init() - stk - } + def apply(initialSize: Int = 32) = new MStackOfInt(initialSize) } -final class MStackOfLong extends MStack[Long]((n: Int) => new Array[Long](n), 0L) +final class MStackOfLong(initialSize: Int) + extends MStack[Long]((n: Int) => new Array[Long](n), 0L, initialSize) object MStackOfLong { - def apply() = { - val stk = new MStackOfLong() - stk.init() - stk - } + def apply(initialSize: Int = 32) = new MStackOfLong(initialSize) } /** @@ -75,11 +71,11 @@ object MStackOfLong { * So we use an Array[AnyRef] as the representation here, and we * convert null to Nope, and an actual object reference to One(x) */ -final class MStackOfMaybe[T <: AnyRef] { +final class MStackOfMaybe[T <: AnyRef](initialSize: Int = 32) { override def toString = delegate.toString - private val delegate = new MStackOf[T] + private val delegate = new MStackOf[T](initialSize) private val nullT = null.asInstanceOf[T] def copyFrom(other: MStackOfMaybe[T]) = delegate.copyFrom(other.delegate) @@ -120,6 +116,7 @@ final class MStackOfMaybe[T <: AnyRef] { def toListMaybe = delegate.toList.map { (x: AnyRef) => Maybe(x) // Scala compiler bug without this cast } + def maxSizeReached = delegate.maxSizeReached } /** @@ -135,7 +132,7 @@ final class MStackOfMaybe[T <: AnyRef] { * an object reference or null, and call Maybe(thing) explicitly outside the * iteration. Maybe(null) is Nope, and Maybe(thing) is One(thing) if thing is not null. */ -final class MStackOf[T <: AnyRef] extends Serializable { +final class MStackOf[T <: AnyRef](initialSize: Int = 32) extends Serializable { override def toString = delegate.toString @@ -143,7 +140,7 @@ final class MStackOf[T <: AnyRef] extends Serializable { @inline final def length = delegate.length - private val delegate = MStackOfAnyRef() + private val delegate = MStackOfAnyRef(initialSize) @inline final def mark = delegate.mark @inline final def reset(m: MStack.Mark) = delegate.reset(m) @@ -156,6 +153,7 @@ final class MStackOf[T <: AnyRef] extends Serializable { @inline final def isEmpty = delegate.isEmpty def clear() = delegate.clear() def toList = delegate.toList + def maxSizeReached = delegate.maxSizeReached def iterator = delegate.iterator.asInstanceOf[ResettableIterator[T]] @@ -163,15 +161,15 @@ final class MStackOf[T <: AnyRef] extends Serializable { } -private[util] final class MStackOfAnyRef private () - extends MStack[AnyRef]((n: Int) => new Array[AnyRef](n), null.asInstanceOf[AnyRef]) +private[util] final class MStackOfAnyRef private (initialSize: Int) + extends MStack[AnyRef]( + (n: Int) => new Array[AnyRef](n), + null.asInstanceOf[AnyRef], + initialSize + ) object MStackOfAnyRef { - def apply() = { - val stk = new MStackOfAnyRef() - stk.init() - stk - } + def apply(initialSize: Int = 32) = new MStackOfAnyRef(initialSize) } /** @@ -184,16 +182,22 @@ object MStackOfAnyRef { */ protected abstract class MStack[@specialized T] private[util] ( arrayAllocator: (Int) => Array[T], - nullValue: T + nullValue: T, + initialSize: Int = 32 ) { private var index = 0 - private var table: Array[T] = null + private var table: Array[T] = arrayAllocator(initialSize) - def init(): Unit = { - index = 0 - table = arrayAllocator(32) - } + private var maxSizeReached_ = 0 + + /** + * The largest this stack's length has ever grown to, across its whole + * lifetime (pops don't reduce it). Diagnostic only, for checking whether + * an initialSize is well-chosen. Always 0 unless + * MStack.trackMaxSizeReached is enabled. + */ + final def maxSizeReached: Int = maxSizeReached_ def copyFrom(other: MStack[T]): Unit = { this.index = other.index @@ -212,6 +216,9 @@ protected abstract class MStack[@specialized T] private[util] ( } } + if (MStack.trackMaxSizeReached && other.maxSizeReached_ > this.maxSizeReached_) { + this.maxSizeReached_ = other.maxSizeReached_ + } } // private var currentIteratorIndex = -1 @@ -250,10 +257,13 @@ protected abstract class MStack[@specialized T] private[util] ( * * @param x The element to push */ - @inline final def push(x: T): Unit = { + inline def push(x: T): Unit = { if (index == table.length) table = growArray(table) table(index) = x index += 1 + if (MStack.trackMaxSizeReached && index > maxSizeReached_) { + maxSizeReached_ = index + } } /** @@ -261,7 +271,7 @@ protected abstract class MStack[@specialized T] private[util] ( * * @return the element on top of the stack */ - @inline final def pop(): T = { + inline def pop(): T = { if (index == 0) Assert.usageError("Stack empty") index -= 1 val x = table(index) @@ -275,7 +285,7 @@ protected abstract class MStack[@specialized T] private[util] ( * * @param x The element to set to the top of the stack */ - @inline final def setTop(x: T): Unit = { + inline def setTop(x: T): Unit = { if (index == 0) Assert.usageError("Stack empty") table(index - 1) = x } @@ -288,9 +298,9 @@ protected abstract class MStack[@specialized T] private[util] ( * * @return the element on top of the stack. */ - @inline final def top: T = table(index - 1) + inline def top: T = table(index - 1) - @inline final def bottom: T = table(0) + inline def bottom: T = table(0) @inline final def isEmpty: Boolean = index == 0 diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetInputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetInputter.scala index 8f89e283cf..2bdb324ccc 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetInputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetInputter.scala @@ -57,7 +57,7 @@ class JDOMInfosetInputter(doc: Document) extends api.infoset.InfosetInputter { * Element. */ private val stack = { - val s = new MStackOf[(Element, Iterator[Content])] + val s = new MStackOf[(Element, Iterator[Content])](16) val docChildren = doc.getContent.iterator if (!docChildren.hasNext) { throw new InvalidInfosetException("Document does not contain a root element") diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetOutputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetOutputter.scala index d0b8c431d3..e7b5bfcb1b 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetOutputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JDOMInfosetOutputter.scala @@ -30,7 +30,7 @@ import org.apache.daffodil.lib.xml.XMLUtils class JDOMInfosetOutputter extends JJDOMInfosetOutputter { - private val stack = new MStackOf[org.jdom2.Parent] + private val stack = new MStackOf[org.jdom2.Parent](16) private var result: Maybe[org.jdom2.Document] = Maybe.Nope private val xsiNS = org.jdom2.Namespace.getNamespace("xsi", XMLUtils.XSI_NAMESPACE.toString) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala index a76b4adc34..2d0c70466b 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala @@ -53,7 +53,7 @@ class JsonInfosetInputter(input: java.io.InputStream) extends api.infoset.Infose * array we enter is pushed onto this stack and is used as the name for all * StartElement events immediately inside that array. */ - private val arrayNameStack = new MStackOf[String]() + private val arrayNameStack = new MStackOf[String](8) /** * The jackson library has no concept of an end event for values (which diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala index 861f49f48d..2f5e153edf 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala @@ -48,7 +48,7 @@ class JsonInfosetOutputter private (writer: java.io.BufferedWriter, pretty: Bool // used to determine if a comma should be written. We write commas before // writing start content if it is not the first child (i.e. top of the stack // is false). - private val isFirstChildStack = MStackOfBoolean() + private val isFirstChildStack = MStackOfBoolean(16) private val stringEncoder = JsonStringEncoder.getInstance() diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetInputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetInputter.scala index f8ecbf9904..27175d4483 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetInputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetInputter.scala @@ -47,7 +47,7 @@ class ScalaXMLInfosetInputter(rootNode: Node) extends api.infoset.InfosetInputte * Start/EndDocument events. */ private val stack = { - val s = new MStackOf[(Elem, Iterator[Node])] + val s = new MStackOf[(Elem, Iterator[Node])](16) val iter = rootNode match { case e: Elem => e.iterator diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetOutputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetOutputter.scala index e30534fcc7..424842b6bf 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetOutputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/ScalaXMLInfosetOutputter.scala @@ -35,7 +35,7 @@ import org.apache.daffodil.lib.xml.XMLUtils class ScalaXMLInfosetOutputter(showFreedInfo: Boolean = false) extends JScalaXMLInfosetOutputter { - protected val stack = new MStackOf[ListBuffer[scala.xml.Node]] + protected val stack = new MStackOf[ListBuffer[scala.xml.Node]](16) private var resultNode: Maybe[scala.xml.Node] = Maybe.Nope def reset() diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetInputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetInputter.scala index 9cedb5f839..e2cb4d1c6f 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetInputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetInputter.scala @@ -52,7 +52,7 @@ class W3CDOMInfosetInputter(doc: Document) extends api.infoset.InfosetInputter { * Element. */ private val stack = { - val s = new MStackOf[(Element, Iterator[Node])] + val s = new MStackOf[(Element, Iterator[Node])](16) val iterator = new IterableNodeList(doc.getChildNodes) if (!iterator.hasNext) { throw new InvalidInfosetException("Document does not contain a root element") diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetOutputter.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetOutputter.scala index b362842b7c..556cae3802 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetOutputter.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/infoset/W3CDOMInfosetOutputter.scala @@ -37,7 +37,7 @@ import org.w3c.dom.Node class W3CDOMInfosetOutputter extends JW3CDOMInfosetOutputter { private var document: Document = null - private val stack = new MStackOf[Node] + private val stack = new MStackOf[Node](16) private var result: Maybe[Document] = Maybe.Nope def reset() diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilParseOutputStreamContentHandler.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilParseOutputStreamContentHandler.scala index 6cb597e734..88b1c90d6f 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilParseOutputStreamContentHandler.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilParseOutputStreamContentHandler.scala @@ -62,9 +62,9 @@ class DaffodilParseOutputStreamContentHandler(out: OutputStream, pretty: Boolean * used to maintain the correct scope of activePrefixMapping throughout processing. It is also used * to reset the activePrefixMapping after processing each element. */ - private lazy val activePrefixMappingContextStack = new MStackOf[NamespaceBinding] + private lazy val activePrefixMappingContextStack = new MStackOf[NamespaceBinding](8) private val outputNewlineStack: MStackOfBoolean = { - val s = MStackOfBoolean() + val s = MStackOfBoolean(16) s.push(false) s } diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilUnparseContentHandlerImpl.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilUnparseContentHandlerImpl.scala index 4a13d5a074..9b6d4a0821 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilUnparseContentHandlerImpl.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/DaffodilUnparseContentHandlerImpl.scala @@ -137,7 +137,7 @@ class DaffodilUnparseContentHandlerImpl(dp: DFDL.DataProcessor, output: DFDL.Out * added in startElement() so they can be quickly removed in endElement() by * popping off the stack. */ - private lazy val prefixMappingTrackingStack = new MStackOf[NamespaceBinding] + private lazy val prefixMappingTrackingStack = new MStackOf[NamespaceBinding](8) /** * SAXInfosetEvents are buffered into this array before sent to the diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala index 521dc2d1f3..bfec3ede75 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala @@ -114,8 +114,8 @@ object MPState { class MPState private () { - val arrayIterationIndexStack = MStackOfLong() - val occursIndexStack = MStackOfLong() + val arrayIterationIndexStack = MStackOfLong(16) + val occursIndexStack = MStackOfLong(16) def moveOverOneArrayIterationIndexOnly() = arrayIterationIndexStack.push(arrayIterationIndexStack.pop() + 1) @@ -131,7 +131,7 @@ class MPState private () { // TODO: it doesn't look anything is actually reading the value of childindex // stack. Can we get rid of it? - val childIndexStack = MStackOfLong() + val childIndexStack = MStackOfLong(16) def moveOverOneElementChildOnly() = childIndexStack.push(childIndexStack.pop() + 1) def childPos = { val res = childIndexStack.top @@ -141,7 +141,7 @@ class MPState private () { val delimiters = new ArrayBuffer[DFADelimiter] val delimitersLocalIndexStack = MStackOfInt() - val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeParserHelper] + val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeParserHelper](16) private def init(): Unit = { arrayIterationIndexStack.push(1L) @@ -225,7 +225,7 @@ final class PState private ( * discard/resetTo/resolvePointOfUncertainty functions to simplify this * logic. */ - val pointsOfUncertainty = new MStackOf[PState.Mark]() + val pointsOfUncertainty = new MStackOf[PState.Mark](16) override def dataStream = One(dataInputStream) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala index 27a4381877..d60290ce79 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala @@ -554,7 +554,7 @@ final class UStateMain private ( // reallyjust need the top for cloning for suspensions, but that // requires changes to how the escape schema cache is accessed, which // isn't a trivial change. - val esClone = new MStackOfMaybe[EscapeSchemeUnparserHelper]() + val esClone = new MStackOfMaybe[EscapeSchemeUnparserHelper](escapeSchemeEVCache.length) esClone.copyFrom(escapeSchemeEVCache) Maybe(esClone) } else { @@ -564,7 +564,7 @@ final class UStateMain private ( if (!delimiterStack.isEmpty) { // If there are any delimiters, then we need to clone them all since // they may be needed for escaping - val dsClone = new MStackOf[DelimiterStackUnparseNode]() + val dsClone = new MStackOf[DelimiterStackUnparseNode](delimiterStack.length) dsClone.copyFrom(delimiterStack) Maybe(dsClone) } else { @@ -663,15 +663,15 @@ final class UStateMain private ( if (currentInfosetNodeStack.isEmpty) Nope else currentInfosetNodeStack.top - override val currentInfosetNodeStack = new MStackOfMaybe[DINode] + override val currentInfosetNodeStack = new MStackOfMaybe[DINode](16) - override val arrayIterationIndexStack = MStackOfLong() + override val arrayIterationIndexStack = MStackOfLong(16) arrayIterationIndexStack.push(1L) override def moveOverOneArrayIterationIndexOnly() = arrayIterationIndexStack.setTop(arrayIterationIndexStack.top + 1) override def arrayIterationPos = arrayIterationIndexStack.top - override val occursIndexStack = MStackOfLong() + override val occursIndexStack = MStackOfLong(16) occursIndexStack.push(1L) override def moveOverOneOccursIndexOnly() = occursIndexStack.setTop(occursIndexStack.top + 1) override def occursPos = occursIndexStack.top @@ -683,12 +683,12 @@ final class UStateMain private ( // TODO: it doesn't look anything is actually reading the value of childindex // stack. Can we get rid of it? - override val childIndexStack = MStackOfLong() + override val childIndexStack = MStackOfLong(16) childIndexStack.push(1L) override def moveOverOneElementChildOnly() = childIndexStack.setTop(childIndexStack.top + 1) override def childPos = childIndexStack.top - override lazy val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeUnparserHelper] + override lazy val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeUnparserHelper](8) val delimiterStack = new MStackOf[DelimiterStackUnparseNode]() override def pushDelimiters(node: DelimiterStackUnparseNode) = delimiterStack.push(node) diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/lib/util/TestMStack.scala b/daffodil-core/src/test/scala/org/apache/daffodil/lib/util/TestMStack.scala index b4cebcdbcb..5905e6deb3 100644 --- a/daffodil-core/src/test/scala/org/apache/daffodil/lib/util/TestMStack.scala +++ b/daffodil-core/src/test/scala/org/apache/daffodil/lib/util/TestMStack.scala @@ -17,6 +17,11 @@ package org.apache.daffodil.lib.util +import org.apache.daffodil.lib.util.Maybe.* + +import org.junit.Assert.* +import org.junit.Test + /** * Compare MStack performance to ArrayStack. It should be faster for primitives */ @@ -24,6 +29,107 @@ class TestMStack { var junk: Long = 0 + // Copying into a sized destination must match copying into a default one. + @Test def testMStackOfCopyFromSizedDestinationMatchesDefault(): Unit = { + val source = new MStackOf[String] + source.push("a") + source.push("b") + source.push("c") + + val sizedDest = new MStackOf[String](source.length) + sizedDest.copyFrom(source) + + val defaultDest = new MStackOf[String] + defaultDest.copyFrom(source) + + assertEquals(defaultDest.toList, sizedDest.toList) + assertEquals(3, sizedDest.length) + assertEquals("c", sizedDest.top) + } + + // A sized-to-exact-depth destination must still grow past capacity. + @Test def testMStackOfSizedDestinationStillGrowsCorrectly(): Unit = { + val source = new MStackOf[String] + source.push("a") + source.push("b") + + val sizedDest = new MStackOf[String](source.length) + sizedDest.copyFrom(source) + + sizedDest.push("c") + sizedDest.push("d") + sizedDest.push("e") + + assertEquals(5, sizedDest.length) + assertEquals("e", sizedDest.top) + assertEquals(List("e", "d", "c", "b", "a"), sizedDest.toList) + } + + /** Same sized-clone pattern, but for MStackOfMaybe. */ + @Test def testMStackOfMaybeCopyFromSizedDestinationMatchesDefault(): Unit = { + val source = new MStackOfMaybe[String] + source.push(One("x")) + source.push(Nope) + source.push(One("z")) + + val sizedDest = new MStackOfMaybe[String](source.length) + sizedDest.copyFrom(source) + + val defaultDest = new MStackOfMaybe[String] + defaultDest.copyFrom(source) + + assertEquals(defaultDest.toListMaybe, sizedDest.toListMaybe) + assertEquals(3, sizedDest.length) + assertEquals(One("z"), sizedDest.top) + assertEquals(One("z"), sizedDest.pop) + assertEquals(Nope, sizedDest.pop) + assertEquals(One("x"), sizedDest.pop) + assertTrue(sizedDest.isEmpty) + } + + /** Same sized-construction pattern, for the primitive-specialized variants. */ + @Test def testMStackOfBooleanSizedConstructionWorks(): Unit = { + val stk = MStackOfBoolean(3) + stk.push(true) + stk.push(false) + stk.push(true) + assertEquals(3, stk.length) + assertEquals(true, stk.pop()) + assertEquals(false, stk.pop()) + assertEquals(true, stk.pop()) + } + + @Test def testMStackOfIntSizedConstructionWorks(): Unit = { + val stk = MStackOfInt(2) + stk.push(1) + stk.push(2) + stk.push(3) + assertEquals(3, stk.length) + assertEquals(3, stk.pop()) + assertEquals(2, stk.pop()) + assertEquals(1, stk.pop()) + } + + @Test def testMStackOfLongSizedConstructionWorks(): Unit = { + val stk = MStackOfLong(1) + stk.push(10L) + stk.push(20L) + assertEquals(2, stk.length) + assertEquals(20L, stk.pop()) + assertEquals(10L, stk.pop()) + } + + // trackMaxSizeReached is a `final val`; confirms it's off by default + // and maxSizeReached stays 0 while it is. + @Test def testMaxSizeReachedDisabledByDefault(): Unit = { + assertEquals(false, MStack.trackMaxSizeReached) + val stk = new MStackOf[String] + stk.push("a") + stk.push("b") + stk.push("c") + assertEquals(0, stk.maxSizeReached) + } + /** * This test compares MStackOfLong to ArrayStack[Long]. *