Skip to content

Commit b21a4f7

Browse files
committed
Document critical learnings for implementing Perl modules with Java backend
Created comprehensive guide capturing all lessons learned from HashUtil implementation: - Method return types must be RuntimeList, not RuntimeScalar - XSLoader modules need super(name, false) constructor parameter - Must use traditional Exporter pattern with require and @isa - Functions must be explicitly listed in @EXPORT_OK - Complete working examples and debugging tips included This documentation will help future developers avoid the same pitfalls when implementing Perl modules with Java backends in PerlOnJava.
1 parent 729d278 commit b21a4f7

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Implementing Perl Modules with Java Backend in PerlOnJava
2+
3+
This document captures critical learnings from implementing Hash::Util and debugging module loading issues.
4+
5+
## Problem Context
6+
7+
When implementing Hash::Util to fix failing tests in op/hash.t, we encountered multiple issues with creating a Perl module backed by Java code. The module needed to provide accurate hash bucket statistics via the `bucket_ratio()` function.
8+
9+
## Key Learnings and Solutions
10+
11+
### 1. Method Return Types Must Be RuntimeList
12+
13+
**Problem:** Methods returning RuntimeScalar were not being registered properly by registerMethod().
14+
15+
**Solution:** All module methods must return `RuntimeList`, not `RuntimeScalar`.
16+
17+
```java
18+
// WRONG - Methods won't be registered
19+
public static RuntimeScalar bucket_ratio(RuntimeArray args, int ctx) {
20+
return new RuntimeScalar(result);
21+
}
22+
23+
// CORRECT - Methods will be registered properly
24+
public static RuntimeList bucket_ratio(RuntimeArray args, int ctx) {
25+
return new RuntimeScalar(result).getList();
26+
}
27+
```
28+
29+
### 2. Constructor Parameter for XSLoader Modules
30+
31+
**Problem:** Modules loaded via XSLoader were not working with `super("Module::Name", true)`.
32+
33+
**Solution:** Modules loaded via XSLoader must use `false` for the auto-export parameter.
34+
35+
```java
36+
// For modules loaded directly (like List::Util)
37+
public ListUtil() {
38+
super("List::Util", true); // true for auto-export
39+
}
40+
41+
// For modules loaded via XSLoader (like Hash::Util)
42+
public HashUtil() {
43+
super("Hash::Util", false); // false because loaded via XSLoader
44+
}
45+
```
46+
47+
### 3. Exporter Pattern in Perl Module
48+
49+
**Problem:** Using `use Exporter 'import'` pattern didn't work correctly.
50+
51+
**Solution:** Use the traditional `require Exporter` with `@ISA` pattern.
52+
53+
```perl
54+
# WRONG - Modern import pattern doesn't work
55+
use Exporter 'import';
56+
57+
# CORRECT - Traditional pattern works
58+
require Exporter;
59+
our @ISA = qw(Exporter);
60+
```
61+
62+
### 4. Explicit @EXPORT_OK Declaration
63+
64+
**Problem:** Trying to dynamically populate @EXPORT_OK from Java didn't work.
65+
66+
**Solution:** Explicitly declare all exportable functions in the Perl module.
67+
68+
```perl
69+
# Must explicitly list all functions
70+
our @EXPORT_OK = qw(
71+
bucket_ratio
72+
lock_keys unlock_keys
73+
lock_hash unlock_hash
74+
hash_seed
75+
);
76+
```
77+
78+
### 5. XSLoader Integration
79+
80+
**Problem:** Functions weren't available in the correct namespace.
81+
82+
**Solution:** Call XSLoader before setting up exports, and ensure Java module registers in correct namespace.
83+
84+
```perl
85+
# Load Java backend FIRST
86+
require XSLoader;
87+
XSLoader::load('HashUtil'); # Loads org.perlonjava.perlmodule.HashUtil
88+
89+
# THEN set up exports
90+
require Exporter;
91+
our @ISA = qw(Exporter);
92+
our @EXPORT_OK = qw(...);
93+
```
94+
95+
### 6. Method Registration in Java
96+
97+
**Problem:** registerMethod() was throwing NoSuchMethodException silently.
98+
99+
**Solution:** Ensure method signatures match exactly what registerMethod() expects.
100+
101+
```java
102+
public static void initialize() {
103+
HashUtil hashUtil = new HashUtil();
104+
try {
105+
// Method name in Java must match registration
106+
hashUtil.registerMethod("bucket_ratio", "bucket_ratio", "\\%");
107+
} catch (NoSuchMethodException e) {
108+
System.err.println("Warning: Missing method: " + e.getMessage());
109+
}
110+
}
111+
```
112+
113+
## Complete Working Example
114+
115+
### Java Backend (HashUtil.java)
116+
117+
```java
118+
package org.perlonjava.perlmodule;
119+
120+
import org.perlonjava.runtime.*;
121+
122+
public class HashUtil extends PerlModuleBase {
123+
124+
public HashUtil() {
125+
super("Hash::Util", false); // false for XSLoader modules
126+
}
127+
128+
public static void initialize() {
129+
HashUtil hashUtil = new HashUtil();
130+
try {
131+
hashUtil.registerMethod("bucket_ratio", "bucket_ratio", "\\%");
132+
// Register other methods...
133+
} catch (NoSuchMethodException e) {
134+
System.err.println("Warning: " + e.getMessage());
135+
}
136+
}
137+
138+
// Methods MUST return RuntimeList
139+
public static RuntimeList bucket_ratio(RuntimeArray args, int ctx) {
140+
// Implementation...
141+
return new RuntimeScalar(result).getList();
142+
}
143+
}
144+
```
145+
146+
### Perl Module (Hash/Util.pm)
147+
148+
```perl
149+
package Hash::Util;
150+
151+
use strict;
152+
use warnings;
153+
require Exporter;
154+
155+
our @ISA = qw(Exporter);
156+
our @EXPORT_OK = qw(
157+
bucket_ratio
158+
lock_keys unlock_keys
159+
lock_hash unlock_hash
160+
hash_seed
161+
);
162+
our $VERSION = '0.28';
163+
164+
# Load the Java backend
165+
require XSLoader;
166+
XSLoader::load('HashUtil');
167+
168+
our %EXPORT_TAGS = (
169+
all => \@EXPORT_OK,
170+
);
171+
172+
1;
173+
```
174+
175+
## Testing the Implementation
176+
177+
```perl
178+
# Test direct call
179+
use Hash::Util;
180+
my %h = (a => 1, b => 2);
181+
print Hash::Util::bucket_ratio(%h), "\n"; # Should work
182+
183+
# Test exported function
184+
use Hash::Util qw(bucket_ratio);
185+
my %h = (a => 1, b => 2);
186+
print bucket_ratio(%h), "\n"; # Should print "2/8" or similar
187+
```
188+
189+
## Common Pitfalls to Avoid
190+
191+
1. **Don't use initializeExporter()** - This is not needed for XSLoader modules
192+
2. **Don't use defineExport()** - Export lists should be in Perl, not Java
193+
3. **Don't return RuntimeScalar** - Always return RuntimeList from module methods
194+
4. **Don't use modern Exporter syntax** - Stick to traditional @ISA pattern
195+
5. **Don't try to dynamically populate @EXPORT_OK** - List functions explicitly
196+
197+
## Debugging Tips
198+
199+
1. Add debug prints in initialize() to verify it's being called:
200+
```java
201+
System.err.println("HashUtil.initialize() called!");
202+
```
203+
204+
2. Check if methods are registered by catching exceptions:
205+
```java
206+
try {
207+
hashUtil.registerMethod("bucket_ratio", "bucket_ratio", "\\%");
208+
System.err.println("Registered bucket_ratio");
209+
} catch (NoSuchMethodException e) {
210+
e.printStackTrace();
211+
}
212+
```
213+
214+
3. Verify functions exist in namespace:
215+
```perl
216+
print "Functions: ";
217+
for my $k (keys %Hash::Util::) {
218+
print "$k " if defined &{"Hash::Util::$k"}
219+
}
220+
```
221+
222+
## Results
223+
224+
Following these patterns, Hash::Util was successfully implemented with:
225+
- bucket_ratio() returning accurate hash statistics
226+
- Proper export functionality via Exporter
227+
- +624 tests fixed in op/hash.t (97.1% pass rate)
228+
229+
## See Also
230+
231+
- ListUtil.java - Example of a module loaded directly (not via XSLoader)
232+
- ScalarUtil.java - Another module implementation for reference
233+
- DBI.java - Example of module with complex initialization

0 commit comments

Comments
 (0)