Skip to content

Commit dee35f1

Browse files
ctruedenclaude
andcommitted
DataHandles: Eliminate reflection hack
This patch replaces the reflection hack with a direct implementation of the modified UTF-8 encoding per the DataOutput spec, allowing the org.scijava.io.handle.WriteBufferDataHandleTest to pass with Java 21. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d14ad9b commit dee35f1

File tree

1 file changed

+31
-37
lines changed

1 file changed

+31
-37
lines changed

src/main/java/org/scijava/io/handle/DataHandles.java

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@
3030
package org.scijava.io.handle;
3131

3232
import java.io.DataOutput;
33-
import java.io.DataOutputStream;
3433
import java.io.IOException;
35-
import java.lang.reflect.InvocationTargetException;
36-
import java.lang.reflect.Method;
34+
import java.io.UTFDataFormatException;
3735

3836
import org.scijava.io.location.Location;
3937
import org.scijava.task.Task;
@@ -46,8 +44,6 @@
4644
*/
4745
public final class DataHandles {
4846

49-
private static Method utfMethod;
50-
5147
private DataHandles() {
5248
// Prevent instantiation of utility class.
5349
}
@@ -74,40 +70,38 @@ private DataHandles() {
7470
public static int writeUTF(final String str, final DataOutput out)
7571
throws IOException
7672
{
77-
// HACK: Strangely, DataOutputStream.writeUTF(String, DataOutput)
78-
// has package-private access. We work around it via reflection.
79-
try {
80-
return (Integer) utfMethod().invoke(null, str, out);
81-
}
82-
catch (final IllegalAccessException | IllegalArgumentException
83-
| InvocationTargetException exc)
84-
{
85-
throw new IllegalStateException(
86-
"Cannot invoke DataOutputStream.writeUTF(String, DataOutput)", exc);
73+
// Encode string as modified UTF-8 per java.io.DataOutput specification.
74+
final int strlen = str.length();
75+
int utflen = 0;
76+
for (int i = 0; i < strlen; i++) {
77+
final char c = str.charAt(i);
78+
if (c >= '\u0001' && c <= '\u007F') utflen += 1;
79+
else if (c <= '\u07FF') utflen += 2;
80+
else utflen += 3;
8781
}
88-
}
89-
90-
// -- Helper methods --
91-
92-
/** Gets the {@link #utfMethod} field, initializing if needed. */
93-
private static Method utfMethod() {
94-
if (utfMethod == null) initUTFMethod();
95-
return utfMethod;
96-
}
97-
98-
/** Initializes the {@link #utfMethod} field. */
99-
private static synchronized void initUTFMethod() {
100-
if (utfMethod != null) return;
101-
try {
102-
final Method m = DataOutputStream.class.getDeclaredMethod("writeUTF",
103-
String.class, DataOutput.class);
104-
m.setAccessible(true);
105-
utfMethod = m;
106-
}
107-
catch (final NoSuchMethodException | SecurityException exc) {
108-
throw new IllegalStateException(
109-
"No usable DataOutputStream.writeUTF(String, DataOutput)", exc);
82+
if (utflen > 65535) throw new UTFDataFormatException(
83+
"encoded string too long: " + utflen + " bytes");
84+
final byte[] bytes = new byte[utflen + 2];
85+
bytes[0] = (byte) ((utflen >>> 8) & 0xFF);
86+
bytes[1] = (byte) (utflen & 0xFF);
87+
int pos = 2;
88+
for (int i = 0; i < strlen; i++) {
89+
final char c = str.charAt(i);
90+
if (c >= '\u0001' && c <= '\u007F') {
91+
bytes[pos++] = (byte) c;
92+
}
93+
else if (c <= '\u07FF') {
94+
bytes[pos++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
95+
bytes[pos++] = (byte) (0x80 | (c & 0x3F));
96+
}
97+
else {
98+
bytes[pos++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
99+
bytes[pos++] = (byte) (0x80 | ((c >> 6) & 0x3F));
100+
bytes[pos++] = (byte) (0x80 | (c & 0x3F));
101+
}
110102
}
103+
out.write(bytes);
104+
return utflen + 2;
111105
}
112106

113107
protected static IOException readOnlyException() {

0 commit comments

Comments
 (0)