|
| 1 | +// SPDX-License-Identifier: EPL-2.0 |
| 2 | +//! Java surface for `java.util.Arrays` static methods (D-526). |
| 3 | +//! |
| 4 | +//! Backend: impl-only |
| 5 | +//! Impl deps: java_array |
| 6 | +//! Clojure peer: none |
| 7 | +//! |
| 8 | +//! The common static surface over cljw's `.array` tag (ADR-0105 JavaArray — |
| 9 | +//! Object[] semantics; Value slots): `asList` (an ArrayList host instance, |
| 10 | +//! the D-294 surface) / `toString` ("[e1, e2, …]") / `copyOf` (nil-padded |
| 11 | +//! like Java's null pad) / `sort` (in-place, natural `compare` order) / |
| 12 | +//! `equals` (element value equality) / `fill`. `binarySearch` / `hashCode` / |
| 13 | +//! the typed-primitive-array overload family are intentionally omitted until |
| 14 | +//! a real consumer needs them (dead-interop lesson). |
| 15 | + |
| 16 | +const std = @import("std"); |
| 17 | +const host_api = @import("../_host_api.zig"); |
| 18 | +const type_descriptor = @import("../../type_descriptor.zig"); |
| 19 | +const Value = @import("../../value/value.zig").Value; |
| 20 | +const Runtime = @import("../../runtime.zig").Runtime; |
| 21 | +const Env = @import("../../env.zig").Env; |
| 22 | +const SourceLocation = @import("../../error/info.zig").SourceLocation; |
| 23 | +const error_catalog = @import("../../error/catalog.zig"); |
| 24 | +const java_array = @import("../../collection/java_array.zig"); |
| 25 | +const array_list_surface = @import("ArrayList.zig"); |
| 26 | +const vector_mod = @import("../../collection/vector.zig"); |
| 27 | +const equal = @import("../../equal.zig"); |
| 28 | +const compare_mod = @import("../../compare.zig"); |
| 29 | +const print_mod = @import("../../print.zig"); |
| 30 | +const string_mod = @import("../../collection/string.zig"); |
| 31 | + |
| 32 | +/// Arity-1 array-argument prelude shared by the statics. |
| 33 | +fn expectArray(v: Value, fn_name: []const u8, loc: SourceLocation) !void { |
| 34 | + if (!java_array.isArray(v)) |
| 35 | + return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = fn_name, .expected = "a Java array", .actual = @tagName(v.tag()) }); |
| 36 | +} |
| 37 | + |
| 38 | +/// `(java.util.Arrays/asList arr)` — a java.util.ArrayList holding the |
| 39 | +/// array's elements. JVM returns a fixed-size List VIEW; cljw returns the |
| 40 | +/// existing ArrayList host surface (mutable, not write-through — the view |
| 41 | +/// aliasing is JVM-internal and no cljw consumer depends on it). |
| 42 | +fn asList(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 43 | + try error_catalog.checkArity("java.util.Arrays/asList", args, 1, loc); |
| 44 | + try expectArray(args[0], "java.util.Arrays/asList", loc); |
| 45 | + const vec = try vector_mod.fromSlice(rt, java_array.asArray(args[0]).items()); |
| 46 | + return array_list_surface.initArrayList(rt, env, &.{vec}, loc); |
| 47 | +} |
| 48 | + |
| 49 | +/// `(java.util.Arrays/toString arr)` — "[e1, e2, …]" with `str`-form |
| 50 | +/// elements (Java String.valueOf). nil array → "null" (JVM parity). |
| 51 | +fn toStringFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 52 | + try error_catalog.checkArity("java.util.Arrays/toString", args, 1, loc); |
| 53 | + if (args[0].tag() == .nil) return string_mod.alloc(rt, "null"); |
| 54 | + try expectArray(args[0], "java.util.Arrays/toString", loc); |
| 55 | + var aw: std.Io.Writer.Allocating = .init(rt.gpa); |
| 56 | + defer aw.deinit(); |
| 57 | + try aw.writer.writeByte('['); |
| 58 | + for (java_array.asArray(args[0]).items(), 0..) |e, i| { |
| 59 | + if (i > 0) try aw.writer.writeAll(", "); |
| 60 | + if (e.tag() == .nil) { |
| 61 | + try aw.writer.writeAll("null"); // Java String.valueOf(null) |
| 62 | + } else { |
| 63 | + try print_mod.writeStrValue(rt, env, &aw.writer, e); |
| 64 | + } |
| 65 | + } |
| 66 | + try aw.writer.writeByte(']'); |
| 67 | + return string_mod.alloc(rt, aw.writer.buffered()); |
| 68 | +} |
| 69 | + |
| 70 | +/// `(java.util.Arrays/copyOf arr n)` — a fresh array of length `n`: truncated |
| 71 | +/// or nil-padded (Java pads Object[] with null). JVM ref: Arrays#copyOf. |
| 72 | +fn copyOf(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 73 | + _ = env; |
| 74 | + try error_catalog.checkArity("java.util.Arrays/copyOf", args, 2, loc); |
| 75 | + try expectArray(args[0], "java.util.Arrays/copyOf", loc); |
| 76 | + const n_i64 = try error_catalog.expectInteger(args[1], "java.util.Arrays/copyOf", loc); |
| 77 | + if (n_i64 < 0) |
| 78 | + return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = "java.util.Arrays/copyOf", .expected = "a non-negative length", .actual = "negative int" }); |
| 79 | + const n: u32 = @intCast(n_i64); |
| 80 | + const out = try java_array.make(rt, n, .nil_val); |
| 81 | + const src = java_array.asArray(args[0]).items(); |
| 82 | + const dst = java_array.asArray(out).items(); |
| 83 | + const keep = @min(src.len, dst.len); |
| 84 | + @memcpy(dst[0..keep], src[0..keep]); |
| 85 | + return out; |
| 86 | +} |
| 87 | + |
| 88 | +const SortCtx = struct { |
| 89 | + rt: *Runtime, |
| 90 | + loc: SourceLocation, |
| 91 | + err: ?anyerror = null, |
| 92 | +}; |
| 93 | + |
| 94 | +fn arrayLessThan(ctx: *SortCtx, a: Value, b: Value) bool { |
| 95 | + if (ctx.err != null) return false; |
| 96 | + const ord = compare_mod.valueCompare(ctx.rt, a, b, ctx.loc) catch |e| { |
| 97 | + ctx.err = e; |
| 98 | + return false; |
| 99 | + }; |
| 100 | + return ord == .lt; |
| 101 | +} |
| 102 | + |
| 103 | +/// `(java.util.Arrays/sort arr)` — IN-PLACE natural-order sort (cljw |
| 104 | +/// `compare`), returning nil like the void Java method. Stable block sort. |
| 105 | +fn sortFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 106 | + _ = env; |
| 107 | + try error_catalog.checkArity("java.util.Arrays/sort", args, 1, loc); |
| 108 | + try expectArray(args[0], "java.util.Arrays/sort", loc); |
| 109 | + var ctx: SortCtx = .{ .rt = rt, .loc = loc }; |
| 110 | + std.mem.sort(Value, java_array.asArray(args[0]).items(), &ctx, arrayLessThan); |
| 111 | + if (ctx.err) |e| return e; |
| 112 | + return .nil_val; |
| 113 | +} |
| 114 | + |
| 115 | +/// `(java.util.Arrays/equals a b)` — same length + element value equality |
| 116 | +/// (nil-safe: two nils true, one nil false). JVM ref: Arrays#equals. |
| 117 | +fn equalsFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 118 | + try error_catalog.checkArity("java.util.Arrays/equals", args, 2, loc); |
| 119 | + const a = args[0]; |
| 120 | + const b = args[1]; |
| 121 | + if (a.tag() == .nil and b.tag() == .nil) return .true_val; |
| 122 | + if (a.tag() == .nil or b.tag() == .nil) return .false_val; |
| 123 | + try expectArray(a, "java.util.Arrays/equals", loc); |
| 124 | + try expectArray(b, "java.util.Arrays/equals", loc); |
| 125 | + const as = java_array.asArray(a).items(); |
| 126 | + const bs = java_array.asArray(b).items(); |
| 127 | + if (as.len != bs.len) return .false_val; |
| 128 | + for (as, bs) |x, y| { |
| 129 | + if (!try equal.valueEqual(rt, env, x, y)) return .false_val; |
| 130 | + } |
| 131 | + return .true_val; |
| 132 | +} |
| 133 | + |
| 134 | +/// `(java.util.Arrays/fill arr v)` — set every slot to `v`; returns nil |
| 135 | +/// (void in Java). JVM ref: Arrays#fill. |
| 136 | +fn fillFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 137 | + _ = rt; |
| 138 | + _ = env; |
| 139 | + try error_catalog.checkArity("java.util.Arrays/fill", args, 2, loc); |
| 140 | + try expectArray(args[0], "java.util.Arrays/fill", loc); |
| 141 | + @memset(java_array.asArray(args[0]).items(), args[1]); |
| 142 | + return .nil_val; |
| 143 | +} |
| 144 | + |
| 145 | +fn initArrays(td: *type_descriptor.TypeDescriptor, gpa: std.mem.Allocator) anyerror!void { |
| 146 | + if (td.method_table.len != 0) return; // idempotent re-run |
| 147 | + const specs = .{ |
| 148 | + .{ "asList", &asList }, |
| 149 | + .{ "toString", &toStringFn }, |
| 150 | + .{ "copyOf", ©Of }, |
| 151 | + .{ "sort", &sortFn }, |
| 152 | + .{ "equals", &equalsFn }, |
| 153 | + .{ "fill", &fillFn }, |
| 154 | + }; |
| 155 | + const entries = try gpa.alloc(type_descriptor.TypeDescriptor.MethodEntry, specs.len); |
| 156 | + inline for (specs, 0..) |spec, i| { |
| 157 | + entries[i] = .{ |
| 158 | + .protocol_name = "", |
| 159 | + .method_name = try gpa.dupe(u8, spec[0]), |
| 160 | + .method_val = Value.initBuiltinFn(spec[1]), |
| 161 | + }; |
| 162 | + } |
| 163 | + td.method_table = entries; |
| 164 | +} |
| 165 | + |
| 166 | +pub const ___HOST_EXTENSION: host_api.Extension = .{ |
| 167 | + .cljw_ns = "cljw.java.util.Arrays", |
| 168 | + .descriptor = &descriptor, |
| 169 | + .init = &initArrays, |
| 170 | +}; |
| 171 | + |
| 172 | +var descriptor: type_descriptor.TypeDescriptor = .{ |
| 173 | + .fqcn = "cljw.java.util.Arrays", |
| 174 | + .kind = .native, |
| 175 | + .field_layout = null, |
| 176 | + .protocol_impls = &.{}, |
| 177 | + .method_table = &.{}, |
| 178 | + .parent = null, |
| 179 | + .meta = .nil_val, |
| 180 | +}; |
0 commit comments