org.xerial
sqlite-jdbc
diff --git a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java
index 02754d7fb..57c529842 100644
--- a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java
+++ b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java
@@ -319,7 +319,19 @@ private static void processShebangLine(String[] args, CompilerOptions parsedArgs
int perlIndex = shebangLine.indexOf("perl");
if (perlIndex != -1) {
String relevantPart = shebangLine.substring(perlIndex + 4).trim();
- String[] shebangArgs = relevantPart.split("\\s+");
+ // Strip emacs mode line marker (e.g. "-*- mode: cperl -*-") which real
+ // perl tolerates in #! lines but not on the command line.
+ int emacsStart = relevantPart.indexOf("-*-");
+ if (emacsStart != -1) {
+ int emacsEnd = relevantPart.indexOf("-*-", emacsStart + 3);
+ if (emacsEnd != -1) {
+ relevantPart = relevantPart.substring(0, emacsStart)
+ + relevantPart.substring(emacsEnd + 3);
+ } else {
+ relevantPart = relevantPart.substring(0, emacsStart);
+ }
+ }
+ String[] shebangArgs = relevantPart.trim().split("\\s+");
// Filter out empty args from shebang processing
String[] nonEmptyArgs = Arrays.stream(shebangArgs)
.filter(arg -> !arg.isEmpty())
diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java
index 61fa880eb..45d16a907 100644
--- a/src/main/java/org/perlonjava/core/Configuration.java
+++ b/src/main/java/org/perlonjava/core/Configuration.java
@@ -33,7 +33,7 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
- public static final String gitCommitId = "5f8a9d5e5";
+ public static final String gitCommitId = "ccf4d54b3";
/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
@@ -48,7 +48,7 @@ public final class Configuration {
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
- public static final String buildTimestamp = "Apr 29 2026 19:52:04";
+ public static final String buildTimestamp = "Apr 29 2026 19:59:08";
// Prevent instantiation
private Configuration() {
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2.java b/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2.java
new file mode 100644
index 000000000..ddda7ee8f
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2.java
@@ -0,0 +1,202 @@
+package org.perlonjava.runtime.perlmodule;
+
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
+import org.perlonjava.runtime.operators.ReferenceOperators;
+import org.perlonjava.runtime.runtimetypes.*;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
+
+/**
+ * Implements the Compress::Bzip2 module on top of Apache Commons Compress.
+ *
+ * The upstream CPAN module (Rob Janes) is XS-only. This Java backend
+ * provides the API surface that pure-Perl callers use:
+ *
+ * - One-shot helpers: memBzip, memBunzip, bzip2, bzunzip
+ * - File handle API: bzopen returning a Compress::Bzip2::bzFile object
+ * - The error-code constants exported by :constants
+ *
+ * Companion class {@link CompressBzip2BzFile} provides the bzread / bzwrite /
+ * bzclose / bzreadline / bzeof methods on the returned handle objects.
+ */
+public class CompressBzip2 extends PerlModuleBase {
+
+ public CompressBzip2() {
+ super("Compress::Bzip2", false);
+ }
+
+ public static void initialize() {
+ CompressBzip2 cb = new CompressBzip2();
+ try {
+ cb.registerMethod("memBzip", null);
+ cb.registerMethod("memBunzip", null);
+ cb.registerMethod("bzip2", null);
+ cb.registerMethod("bzunzip", null);
+ cb.registerMethod("bzopen", null);
+
+ // libbzip2 status / mode constants. The numeric values mirror the
+ // upstream Compress::Bzip2 / libbzip2 headers so callers comparing
+ // against e.g. BZ_STREAM_END keep working.
+ cb.registerMethod("BZ_OK", null);
+ cb.registerMethod("BZ_RUN_OK", null);
+ cb.registerMethod("BZ_FLUSH_OK", null);
+ cb.registerMethod("BZ_FINISH_OK", null);
+ cb.registerMethod("BZ_STREAM_END", null);
+ cb.registerMethod("BZ_SEQUENCE_ERROR", null);
+ cb.registerMethod("BZ_PARAM_ERROR", null);
+ cb.registerMethod("BZ_MEM_ERROR", null);
+ cb.registerMethod("BZ_DATA_ERROR", null);
+ cb.registerMethod("BZ_DATA_ERROR_MAGIC", null);
+ cb.registerMethod("BZ_IO_ERROR", null);
+ cb.registerMethod("BZ_UNEXPECTED_EOF", null);
+ cb.registerMethod("BZ_OUTBUFF_FULL", null);
+ cb.registerMethod("BZ_CONFIG_ERROR", null);
+ cb.registerMethod("BZ_RUN", null);
+ cb.registerMethod("BZ_FLUSH", null);
+ cb.registerMethod("BZ_FINISH", null);
+ cb.registerMethod("BZ_MAX_UNUSED", null);
+ } catch (NoSuchMethodException e) {
+ System.err.println("Warning: Missing Compress::Bzip2 method: " + e.getMessage());
+ }
+
+ CompressBzip2BzFile.initialize();
+ }
+
+ public static RuntimeList BZ_OK(RuntimeArray args, int ctx) { return new RuntimeScalar(0).getList(); }
+ public static RuntimeList BZ_RUN_OK(RuntimeArray args, int ctx) { return new RuntimeScalar(1).getList(); }
+ public static RuntimeList BZ_FLUSH_OK(RuntimeArray args, int ctx) { return new RuntimeScalar(2).getList(); }
+ public static RuntimeList BZ_FINISH_OK(RuntimeArray args, int ctx) { return new RuntimeScalar(3).getList(); }
+ public static RuntimeList BZ_STREAM_END(RuntimeArray args, int ctx) { return new RuntimeScalar(4).getList(); }
+ public static RuntimeList BZ_SEQUENCE_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-1).getList(); }
+ public static RuntimeList BZ_PARAM_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-2).getList(); }
+ public static RuntimeList BZ_MEM_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-3).getList(); }
+ public static RuntimeList BZ_DATA_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-4).getList(); }
+ public static RuntimeList BZ_DATA_ERROR_MAGIC(RuntimeArray args, int ctx) { return new RuntimeScalar(-5).getList(); }
+ public static RuntimeList BZ_IO_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-6).getList(); }
+ public static RuntimeList BZ_UNEXPECTED_EOF(RuntimeArray args, int ctx) { return new RuntimeScalar(-7).getList(); }
+ public static RuntimeList BZ_OUTBUFF_FULL(RuntimeArray args, int ctx) { return new RuntimeScalar(-8).getList(); }
+ public static RuntimeList BZ_CONFIG_ERROR(RuntimeArray args, int ctx) { return new RuntimeScalar(-9).getList(); }
+ public static RuntimeList BZ_RUN(RuntimeArray args, int ctx) { return new RuntimeScalar(0).getList(); }
+ public static RuntimeList BZ_FLUSH(RuntimeArray args, int ctx) { return new RuntimeScalar(1).getList(); }
+ public static RuntimeList BZ_FINISH(RuntimeArray args, int ctx) { return new RuntimeScalar(2).getList(); }
+ public static RuntimeList BZ_MAX_UNUSED(RuntimeArray args, int ctx) { return new RuntimeScalar(5000).getList(); }
+
+ /**
+ * Treat a scalar (or scalar reference) as a byte string. Mirrors the
+ * helper in {@link CompressZlib}.
+ */
+ private static byte[] getInputBytes(RuntimeScalar dataScalar) {
+ RuntimeScalar actual = dataScalar;
+ if (dataScalar.type == RuntimeScalarType.REFERENCE) {
+ actual = dataScalar.scalarDeref();
+ }
+ return actual.toString().getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ private static RuntimeScalar bytesToScalar(byte[] bytes, int len) {
+ RuntimeScalar s = new RuntimeScalar(new String(bytes, 0, len, StandardCharsets.ISO_8859_1));
+ s.type = RuntimeScalarType.BYTE_STRING;
+ return s;
+ }
+
+ /**
+ * memBzip($data) — one-shot compression. Returns the bzip2 stream or
+ * undef on error (matching the upstream module).
+ */
+ public static RuntimeList memBzip(RuntimeArray args, int ctx) {
+ if (args.isEmpty()) return scalarUndef.getList();
+ byte[] input = getInputBytes(args.get(0));
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.max(64, input.length / 2));
+ try (BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(baos)) {
+ out.write(input);
+ }
+ byte[] result = baos.toByteArray();
+ return bytesToScalar(result, result.length).getList();
+ } catch (IOException e) {
+ return scalarUndef.getList();
+ }
+ }
+
+ /**
+ * memBunzip($data) — one-shot decompression. Returns the inflated bytes
+ * or undef on error.
+ */
+ public static RuntimeList memBunzip(RuntimeArray args, int ctx) {
+ if (args.isEmpty()) return scalarUndef.getList();
+ byte[] input = getInputBytes(args.get(0));
+ try (BZip2CompressorInputStream in = new BZip2CompressorInputStream(
+ new ByteArrayInputStream(input), true)) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length * 4);
+ byte[] buf = new byte[4096];
+ int n;
+ while ((n = in.read(buf)) != -1) {
+ baos.write(buf, 0, n);
+ }
+ byte[] result = baos.toByteArray();
+ return bytesToScalar(result, result.length).getList();
+ } catch (IOException e) {
+ return scalarUndef.getList();
+ }
+ }
+
+ /** bzip2($data) — alias of memBzip in the upstream module. */
+ public static RuntimeList bzip2(RuntimeArray args, int ctx) {
+ return memBzip(args, ctx);
+ }
+
+ /** bzunzip($data) — alias of memBunzip. */
+ public static RuntimeList bzunzip(RuntimeArray args, int ctx) {
+ return memBunzip(args, ctx);
+ }
+
+ /**
+ * bzopen($filename, $mode) — opens a bzip2 file for reading ('rb') or
+ * writing ('wb'). Returns a blessed Compress::Bzip2::bzFile handle, or
+ * undef on error. Method-call syntax (Compress::Bzip2->bzopen) is
+ * also accepted.
+ */
+ public static RuntimeList bzopen(RuntimeArray args, int ctx) {
+ int argOffset = 0;
+ if (!args.isEmpty()) {
+ String first = args.get(0).toString();
+ if (first.equals("Compress::Bzip2") || first.contains("::")) {
+ argOffset = 1;
+ }
+ }
+ if (args.size() < argOffset + 2) return scalarUndef.getList();
+
+ String filename = args.get(argOffset).toString();
+ String mode = args.get(argOffset + 1).toString();
+
+ try {
+ RuntimeHash self = new RuntimeHash();
+ self.put("_mode", new RuntimeScalar(mode));
+ self.put("_eof", new RuntimeScalar(0));
+
+ if (mode.startsWith("r")) {
+ // Concatenated streams (true) so multi-block .bz2 archives
+ // (typical for tarballs) are read end-to-end.
+ InputStream fis = new FileInputStream(filename);
+ BZip2CompressorInputStream in = new BZip2CompressorInputStream(fis, true);
+ self.put("_stream", new RuntimeScalar(in));
+ } else if (mode.startsWith("w")) {
+ OutputStream fos = new FileOutputStream(filename);
+ BZip2CompressorOutputStream out = new BZip2CompressorOutputStream(fos);
+ self.put("_stream", new RuntimeScalar(out));
+ } else {
+ return scalarUndef.getList();
+ }
+
+ RuntimeScalar ref = self.createReference();
+ ReferenceOperators.bless(ref, new RuntimeScalar("Compress::Bzip2::bzFile"));
+ return ref.getList();
+ } catch (IOException e) {
+ return scalarUndef.getList();
+ }
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2BzFile.java b/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2BzFile.java
new file mode 100644
index 000000000..f14528e8c
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2BzFile.java
@@ -0,0 +1,188 @@
+package org.perlonjava.runtime.perlmodule;
+
+import org.perlonjava.runtime.runtimetypes.*;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
+
+/**
+ * Implements the Compress::Bzip2::bzFile class returned by
+ * {@link CompressBzip2#bzopen}. Provides bzread / bzwrite / bzreadline /
+ * bzeof / bzclose / bzerror methods, matching the OO API of the upstream
+ * Compress::Bzip2 CPAN module.
+ *
+ * Mirrors the pattern of {@link CompressZlibGzFile} for consistency.
+ */
+public class CompressBzip2BzFile extends PerlModuleBase {
+
+ private static final String STREAM_KEY = "_stream";
+ private static final String EOF_KEY = "_eof";
+
+ public CompressBzip2BzFile() {
+ super("Compress::Bzip2::bzFile", false);
+ }
+
+ public static void initialize() {
+ CompressBzip2BzFile bz = new CompressBzip2BzFile();
+ try {
+ bz.registerMethod("bzread", null);
+ bz.registerMethod("bzwrite", null);
+ bz.registerMethod("bzreadline", null);
+ bz.registerMethod("bzeof", null);
+ bz.registerMethod("bzclose", null);
+ bz.registerMethod("bzerror", null);
+ } catch (NoSuchMethodException e) {
+ System.err.println("Warning: Missing Compress::Bzip2::bzFile method: " + e.getMessage());
+ }
+ }
+
+ /**
+ * $bz->bzread($buffer [, $size]) — fills $buffer in place and returns the
+ * number of bytes read (0 on EOF, -1 on error).
+ */
+ public static RuntimeList bzread(RuntimeArray args, int ctx) {
+ if (args.size() < 2) return new RuntimeScalar(-1).getList();
+
+ RuntimeHash self = args.get(0).hashDeref();
+ int nbytes = args.size() >= 3 ? args.get(2).getInt() : 4096;
+
+ RuntimeScalar streamScalar = self.get(STREAM_KEY);
+ if (streamScalar == null || streamScalar.type != RuntimeScalarType.JAVAOBJECT
+ || !(streamScalar.value instanceof InputStream is)) {
+ return new RuntimeScalar(-1).getList();
+ }
+
+ try {
+ byte[] buf = new byte[nbytes];
+ int totalRead = 0;
+ while (totalRead < nbytes) {
+ int n = is.read(buf, totalRead, nbytes - totalRead);
+ if (n == -1) {
+ self.put(EOF_KEY, new RuntimeScalar(1));
+ break;
+ }
+ totalRead += n;
+ }
+
+ if (totalRead == 0) {
+ args.get(1).set("");
+ return new RuntimeScalar(0).getList();
+ }
+
+ String data = new String(buf, 0, totalRead, StandardCharsets.ISO_8859_1);
+ args.get(1).set(data);
+ return new RuntimeScalar(totalRead).getList();
+ } catch (IOException e) {
+ return new RuntimeScalar(-1).getList();
+ }
+ }
+
+ /** $bz->bzwrite($data) — returns bytes written or 0 on error. */
+ public static RuntimeList bzwrite(RuntimeArray args, int ctx) {
+ if (args.size() < 2) return new RuntimeScalar(0).getList();
+
+ RuntimeHash self = args.get(0).hashDeref();
+ String data = args.get(1).toString();
+
+ RuntimeScalar streamScalar = self.get(STREAM_KEY);
+ if (streamScalar == null || streamScalar.type != RuntimeScalarType.JAVAOBJECT
+ || !(streamScalar.value instanceof OutputStream os)) {
+ return new RuntimeScalar(0).getList();
+ }
+
+ try {
+ byte[] bytes = data.getBytes(StandardCharsets.ISO_8859_1);
+ os.write(bytes);
+ return new RuntimeScalar(bytes.length).getList();
+ } catch (IOException e) {
+ return new RuntimeScalar(0).getList();
+ }
+ }
+
+ /**
+ * $bz->bzreadline($line) — reads one line into $line in place, returns
+ * length read (0 on EOF, -1 on error).
+ */
+ public static RuntimeList bzreadline(RuntimeArray args, int ctx) {
+ if (args.size() < 2) return new RuntimeScalar(-1).getList();
+
+ RuntimeHash self = args.get(0).hashDeref();
+
+ RuntimeScalar streamScalar = self.get(STREAM_KEY);
+ if (streamScalar == null || streamScalar.type != RuntimeScalarType.JAVAOBJECT
+ || !(streamScalar.value instanceof InputStream is)) {
+ return new RuntimeScalar(-1).getList();
+ }
+
+ try {
+ StringBuilder line = new StringBuilder();
+ int c;
+ while ((c = is.read()) != -1) {
+ line.append((char) c);
+ if (c == '\n') break;
+ }
+
+ if (line.isEmpty()) {
+ self.put(EOF_KEY, new RuntimeScalar(1));
+ args.get(1).set("");
+ return new RuntimeScalar(0).getList();
+ }
+
+ String result = line.toString();
+ args.get(1).set(result);
+ return new RuntimeScalar(result.length()).getList();
+ } catch (IOException e) {
+ return new RuntimeScalar(-1).getList();
+ }
+ }
+
+ /** $bz->bzeof — 1 if at end of stream, 0 otherwise. */
+ public static RuntimeList bzeof(RuntimeArray args, int ctx) {
+ if (args.isEmpty()) return new RuntimeScalar(0).getList();
+ RuntimeHash self = args.get(0).hashDeref();
+ RuntimeScalar eofScalar = self.get(EOF_KEY);
+ return new RuntimeScalar(eofScalar != null ? eofScalar.getInt() : 0).getList();
+ }
+
+ /** $bz->bzclose — closes the stream. Returns 0 (BZ_OK) on success, -1 on error. */
+ public static RuntimeList bzclose(RuntimeArray args, int ctx) {
+ if (args.isEmpty()) return new RuntimeScalar(-1).getList();
+
+ RuntimeHash self = args.get(0).hashDeref();
+ RuntimeScalar streamScalar = self.get(STREAM_KEY);
+ if (streamScalar == null || streamScalar.type != RuntimeScalarType.JAVAOBJECT) {
+ return new RuntimeScalar(-1).getList();
+ }
+
+ try {
+ Object stream = streamScalar.value;
+ if (stream instanceof OutputStream os) {
+ os.flush();
+ os.close();
+ } else if (stream instanceof InputStream is) {
+ is.close();
+ }
+ self.put(STREAM_KEY, scalarUndef);
+ self.put(EOF_KEY, new RuntimeScalar(1));
+ return new RuntimeScalar(0).getList();
+ } catch (IOException e) {
+ return new RuntimeScalar(-1).getList();
+ }
+ }
+
+ /**
+ * $bz->bzerror — no error tracking; always reports success. Returns
+ * "" in scalar context, ("", 0) in list context (matching upstream).
+ */
+ public static RuntimeList bzerror(RuntimeArray args, int ctx) {
+ if (ctx == RuntimeContextType.LIST) {
+ RuntimeList result = new RuntimeList();
+ result.add(new RuntimeScalar(""));
+ result.add(new RuntimeScalar(0));
+ return result;
+ }
+ return new RuntimeScalar("").getList();
+ }
+}
diff --git a/src/main/perl/lib/Compress/Bzip2.pm b/src/main/perl/lib/Compress/Bzip2.pm
new file mode 100644
index 000000000..69b796d81
--- /dev/null
+++ b/src/main/perl/lib/Compress/Bzip2.pm
@@ -0,0 +1,63 @@
+package Compress::Bzip2;
+
+#
+# Original Compress::Bzip2 module by Rob Janes.
+# Copyright (c) 2005 Rob Janes. All rights reserved.
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# PerlOnJava implementation by Flavio S. Glock.
+# The implementation is in:
+# src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2.java
+# src/main/java/org/perlonjava/runtime/perlmodule/CompressBzip2BzFile.java
+# It is backed by Apache Commons Compress (BZip2CompressorInputStream /
+# BZip2CompressorOutputStream).
+#
+
+use strict;
+use warnings;
+
+our $VERSION = '2.28';
+
+require Exporter;
+our @ISA = qw(Exporter);
+
+XSLoader::load('Compress::Bzip2');
+
+our %EXPORT_TAGS = (
+ 'constants' => [ qw(
+ BZ_CONFIG_ERROR BZ_DATA_ERROR BZ_DATA_ERROR_MAGIC
+ BZ_FINISH BZ_FINISH_OK BZ_FLUSH BZ_FLUSH_OK
+ BZ_IO_ERROR BZ_MAX_UNUSED BZ_MEM_ERROR
+ BZ_OK BZ_OUTBUFF_FULL BZ_PARAM_ERROR
+ BZ_RUN BZ_RUN_OK BZ_SEQUENCE_ERROR
+ BZ_STREAM_END BZ_UNEXPECTED_EOF
+ ) ],
+ 'utilities' => [ qw(
+ memBzip memBunzip bzip2 bzunzip
+ ) ],
+ 'bzip1' => [ qw(bzopen bzclose bzread bzreadline bzwrite bzeof bzerror) ],
+ 'gzip' => [ qw(bzopen bzclose bzread bzreadline bzwrite bzeof bzerror) ],
+);
+our @EXPORT_OK = ( map { @$_ } values %EXPORT_TAGS );
+$EXPORT_TAGS{'all'} = [ @EXPORT_OK ];
+our @EXPORT = qw();
+
+1;
+
+__END__
+
+=head1 NAME
+
+Compress::Bzip2 - PerlOnJava implementation of Compress::Bzip2
+
+=head1 DESCRIPTION
+
+Provides bzip2 compression and decompression backed by the Apache Commons
+Compress Java library. The Perl-visible API matches the upstream
+L CPAN module: one-shot helpers (C, C,
+C, C) and a file-handle interface returned by C
+with C, C, C, C, C, and
+C.
+
+=cut