Class InMemoryConstructOutputSink

java.lang.Object
org.ek9lang.compiler.backend.InMemoryConstructOutputSink
All Implemented Interfaces:
ConstructOutputSink

public final class InMemoryConstructOutputSink extends Object implements ConstructOutputSink
Holds generated classes in memory instead of writing a file per construct.

At 1M SLOC the output is 1,255 MB against a 30,752 MB live heap, and holding it measured as no memory cost at all (max RSS moved -0.1%) - the bytes displace the page cache the writes were dirtying. It avoids 254,724 file creations and the 254,724 reads packaging used to do to build the jar from them.

toZipEntries(Path) produces exactly the entry names the file-based path produces, because both compute outputRoot.toAbsolutePath().relativize(target.toAbsolutePath()).toString() and hand it to zip.getPath(...) - see Packager.addFileBasedSet. That equivalence is what lets the change be verified by byte-comparing the resulting jar rather than by argument.

Keyed on the normalised absolute Path, not on File. File.equals compares path STRINGS, so new File(dir, "a/B.class") and an equivalent path built any other way are unequal objects for the same file. Writers and readers here do not construct their paths identically - ByteCodeDirectiveListener rebuilds the path from the symbol's FQN - so a File key would make read(File) silently miss.

  • Constructor Details

    • InMemoryConstructOutputSink

      public InMemoryConstructOutputSink()
  • Method Details

    • write

      public void write(File target, byte[] content)
      Description copied from interface: ConstructOutputSink
      Accept one generated class.
      Specified by:
      write in interface ConstructOutputSink
      Parameters:
      target - the file this class WOULD occupy. It is the identity of the output, not necessarily a path that gets written: a jar sink relativises it to an entry name, an in-memory sink uses it as a key. Keeping it a File means the naming stays identical to the pre-sink pipeline, which is what makes byte-identical output verifiable.
      content - the class bytes.
    • read

      public Optional<byte[]> read(File target)
      Description copied from interface: ConstructOutputSink
      Read back a class this sink was given, if it has it.

      This exists because the sink is the single source of generated bytes, and something inside the compile needs to read them back: ByteCodeDirectiveListener validates @BYTECODE directives during compilation and used to go straight to the filesystem. That is correct only while the bytes are on the filesystem, so it would fail - loudly, but for a confusing reason - the moment a caller chose a sink that holds them. Asking the sink removes the question rather than documenting it.

      Specified by:
      read in interface ConstructOutputSink
      Parameters:
      target - the same file identity that was passed to ConstructOutputSink.write(File, byte[]).
      Returns:
      the bytes, or empty if this sink does not have them.
    • toZipEntries

      public List<ZipBinaryContent> toZipEntries(Path outputRoot)
      The generated classes that live UNDER the given output root, as zip entries named relative to it.

      The filter is not incidental. One sink serves a whole compilation, and a dev build writes into two roots - generated/main/jvm and generated/dev/jvm - which the packager adds as two separate ZipSets. Relativising every entry against whichever root was asked for would name the other root's classes ../dev/..., quietly producing a jar with entries outside its own tree.

      Parameters:
      outputRoot - the directory the file-based pipeline would have written into.
    • flushTo

      public int flushTo(ConstructOutputSink fileSink)
      Write everything this sink is holding out to the files it would have occupied.

      For the paths that genuinely need the exploded output rather than the jar - the -t test runner reads generated/dev/jvm directly - the bytes still have to land on disk. Holding them and flushing once keeps the batch path free of the 254,724 writes while leaving those consumers working.

      Parameters:
      fileSink - the sink that actually writes - normally a FileConstructOutputSink.
      Returns:
      how many files were written.
    • count

      public int count()