Skip to content

Commit 4763778

Browse files
committed
Merge branch 'main' into memory-store
# Conflicts: # src/test/java/dev/zarr/zarrjava/ZarrV2Test.java
2 parents fa2626b + c5cd02e commit 4763778

39 files changed

+1081
-135
lines changed

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
# Early preview of `zarr-java`
1+
# zarr-java
22

3-
This repository contains an early preview of a Java implementation of the Zarr specification.
4-
It is intended for collecting feedback from the community and not for use. The API is subject to changes.
5-
6-
Refer to [JZarr](https://github.com/zarr-developers/jzarr) for a stable implementation of Zarr version 2.
3+
This repository contains a Java implementation of Zarr version 2 and 3.
74

85
## Usage
96
```java
@@ -50,4 +47,4 @@ Furthermore, you will need the `l4_sample` test data:
5047
`curl https://static.webknossos.org/data/zarr_v3/l4_sample.zip -o testdata/l4_sample.zip
5148
&& cd testdata
5249
&& unzip l4_sample.zip
53-
`
50+
`

src/main/java/dev/zarr/zarrjava/core/ArrayMetadata.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.zarr.zarrjava.core;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
45
import com.fasterxml.jackson.annotation.JsonProperty;
56
import dev.zarr.zarrjava.ZarrException;
67
import dev.zarr.zarrjava.utils.MultiArrayUtils;
@@ -16,6 +17,7 @@ public abstract class ArrayMetadata {
1617

1718
public final long[] shape;
1819

20+
@JsonInclude(JsonInclude.Include.ALWAYS)
1921
@JsonProperty("fill_value")
2022
public final Object fillValue;
2123
@JsonIgnore
@@ -41,6 +43,8 @@ public int ndim() {
4143

4244
public abstract Object parsedFillValue();
4345

46+
public @Nonnull abstract Attributes attributes() throws ZarrException;
47+
4448
public static Object parseFillValue(Object fillValue, @Nonnull DataType dataType)
4549
throws ZarrException {
4650
if (fillValue == null) {
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package dev.zarr.zarrjava.core;
2+
3+
import dev.zarr.zarrjava.ZarrException;
4+
5+
import javax.annotation.Nonnull;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.function.Function;
10+
11+
public class Attributes extends HashMap<String, Object> {
12+
13+
public Attributes() {
14+
super();
15+
}
16+
17+
public Attributes (Function<Attributes, Attributes> attributeMapper) {
18+
super();
19+
attributeMapper.apply(this);
20+
}
21+
22+
23+
public Attributes(Map<String, Object> attributes) {
24+
super(attributes);
25+
}
26+
27+
public Attributes set(String s, Object o){
28+
this.put(s, o);
29+
return this;
30+
}
31+
32+
public Attributes delete(String s){
33+
this.remove(s);
34+
return this;
35+
}
36+
37+
public boolean getBoolean(String key) {
38+
Object value = this.get(key);
39+
if (value instanceof Boolean) {
40+
return (Boolean) value;
41+
}
42+
throw new IllegalArgumentException("Value for key " + key + " is not a Boolean");
43+
}
44+
45+
public int getInt(String key) {
46+
Object value = this.get(key);
47+
if (value instanceof Number) {
48+
return ((Number) value).intValue();
49+
}
50+
throw new IllegalArgumentException("Value for key " + key + " is not an Integer");
51+
}
52+
53+
public double getDouble(String key) {
54+
Object value = this.get(key);
55+
if (value instanceof Number) {
56+
return ((Number) value).doubleValue();
57+
}
58+
throw new IllegalArgumentException("Value for key " + key + " is not a Double");
59+
}
60+
61+
public float getFloat(String key) {
62+
Object value = this.get(key);
63+
if (value instanceof Number) {
64+
return ((Number) value).floatValue();
65+
}
66+
throw new IllegalArgumentException("Value for key " + key + " is not a Float");
67+
}
68+
69+
@Nonnull
70+
public String getString(String key) {
71+
Object value = this.get(key);
72+
if (value instanceof String) {
73+
return (String) value;
74+
}
75+
throw new IllegalArgumentException("Value for key " + key + " is not a String");
76+
}
77+
78+
@Nonnull
79+
public List<Object> getList(String key) {
80+
Object value = this.get(key);
81+
if (value instanceof List) {
82+
return (List<Object>) value;
83+
}
84+
throw new IllegalArgumentException("Value for key " + key + " is not a List");
85+
}
86+
87+
public Attributes getAttributes(String key) {
88+
Object value = this.get(key);
89+
if (value instanceof Attributes) {
90+
return (Attributes) value;
91+
}
92+
if (value instanceof Map) {
93+
return new Attributes((Map<String, Object>) value);
94+
}
95+
throw new IllegalArgumentException("Value for key " + key + " is not an Attributes object");
96+
}
97+
98+
public <T> T[] getArray(String key, Class<T> clazz) throws ZarrException {
99+
Object value = this.get(key);
100+
if (value instanceof Object[] && (((Object[]) value).length == 0 || clazz.isInstance(((Object[]) value)[0]) )) {
101+
return (T[]) value;
102+
}
103+
if (value instanceof List) {
104+
List<?> list = (List<?>) value;
105+
@SuppressWarnings("unchecked")
106+
T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, list.size());
107+
for (int i = 0; i < list.size(); i++) {
108+
Object elem = list.get(i);
109+
if (clazz.isInstance(elem)) {
110+
array[i] = clazz.cast(elem);
111+
} else {
112+
// Try to find a constructor that takes the element's class
113+
java.lang.reflect.Constructor<?> matched = null;
114+
for (java.lang.reflect.Constructor<?> c : clazz.getConstructors()) {
115+
Class<?>[] params = c.getParameterTypes();
116+
if (params.length == 1 && params[0].isAssignableFrom(elem.getClass())) {
117+
matched = c;
118+
break;
119+
}
120+
}
121+
if (matched != null) {
122+
try {
123+
array[i] = (T) matched.newInstance(elem);
124+
} catch (Exception e) {
125+
throw new ZarrException("Failed to convert element at index " + i + " to type " + clazz.getName(), e);
126+
}
127+
} else {
128+
throw new IllegalArgumentException("Element at index " + i + " is not of type " + clazz.getName() + " and no suitable constructor found for conversion of type " + elem.getClass().getName());
129+
}
130+
}
131+
}
132+
return array;
133+
}
134+
throw new IllegalArgumentException("Value for key " + key + " is not a List or array of type " + clazz.getName());
135+
}
136+
137+
public int[] getIntArray(String key) {
138+
Object value = this.get(key);
139+
if (value instanceof int[]) {
140+
return (int[]) value;
141+
}
142+
if (value instanceof List) {
143+
List<?> list = (List<?>) value;
144+
int[] array = new int[list.size()];
145+
for (int i = 0; i < list.size(); i++) {
146+
Object elem = list.get(i);
147+
if (elem instanceof Number) {
148+
array[i] = ((Number) elem).intValue();
149+
} else {
150+
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
151+
}
152+
}
153+
return array;
154+
}
155+
throw new IllegalArgumentException("Value for key " + key + " is not an int array or List");
156+
}
157+
158+
public long[] getLongArray(String key) {
159+
Object value = this.get(key);
160+
if (value instanceof long[]) {
161+
return (long[]) value;
162+
}
163+
if (value instanceof List) {
164+
List<?> list = (List<?>) value;
165+
long[] array = new long[list.size()];
166+
for (int i = 0; i < list.size(); i++) {
167+
Object elem = list.get(i);
168+
if (elem instanceof Number) {
169+
array[i] = ((Number) elem).longValue();
170+
} else {
171+
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
172+
}
173+
}
174+
return array;
175+
}
176+
throw new IllegalArgumentException("Value for key " + key + " is not a long array or List");
177+
}
178+
179+
public double[] getDoubleArray(String key) {
180+
Object value = this.get(key);
181+
if (value instanceof double[]) {
182+
return (double[]) value;
183+
}
184+
if (value instanceof List) {
185+
List<?> list = (List<?>) value;
186+
double[] array = new double[list.size()];
187+
for (int i = 0; i < list.size(); i++) {
188+
Object elem = list.get(i);
189+
if (elem instanceof Number) {
190+
array[i] = ((Number) elem).doubleValue();
191+
} else {
192+
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
193+
}
194+
}
195+
return array;
196+
}
197+
throw new IllegalArgumentException("Value for key " + key + " is not a double array or List");
198+
}
199+
200+
public float[] getFloatArray(String key) {
201+
Object value = this.get(key);
202+
if (value instanceof float[]) {
203+
return (float[]) value;
204+
}
205+
if (value instanceof List) {
206+
List<?> list = (List<?>) value;
207+
float[] array = new float[list.size()];
208+
for (int i = 0; i < list.size(); i++) {
209+
Object elem = list.get(i);
210+
if (elem instanceof Number) {
211+
array[i] = ((Number) elem).floatValue();
212+
} else {
213+
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
214+
}
215+
}
216+
return array;
217+
}
218+
throw new IllegalArgumentException("Value for key " + key + " is not a float array or List");
219+
}
220+
221+
public boolean[] getBooleanArray(String key) {
222+
Object value = this.get(key);
223+
if (value instanceof boolean[]) {
224+
return (boolean[]) value;
225+
}
226+
if (value instanceof List) {
227+
List<?> list = (List<?>) value;
228+
boolean[] array = new boolean[list.size()];
229+
for (int i = 0; i < list.size(); i++) {
230+
Object elem = list.get(i);
231+
if (elem instanceof Boolean) {
232+
array[i] = (Boolean) elem;
233+
} else {
234+
throw new IllegalArgumentException("Element at index " + i + " is not a Boolean");
235+
}
236+
}
237+
return array;
238+
}
239+
throw new IllegalArgumentException("Value for key " + key + " is not a boolean array or List");
240+
}
241+
}

src/main/java/dev/zarr/zarrjava/core/Group.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,6 @@ public Node[] listAsArray() {
8383
return nodeStream.toArray(Node[]::new);
8484
}
8585
}
86+
87+
public abstract GroupMetadata metadata();
8688
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
package dev.zarr.zarrjava.core;
22

3-
public abstract class GroupMetadata {}
3+
import javax.annotation.Nonnull;
4+
5+
import dev.zarr.zarrjava.ZarrException;
6+
7+
public abstract class GroupMetadata {
8+
9+
public @Nonnull abstract Attributes attributes() throws ZarrException;
10+
11+
}

src/main/java/dev/zarr/zarrjava/core/Node.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface Node {
1313

1414
String ZARR_JSON = "zarr.json";
1515
String ZARRAY = ".zarray";
16+
String ZATTRS = ".zattrs";
1617
String ZGROUP = ".zgroup";
1718

1819
/**

src/main/java/dev/zarr/zarrjava/core/codec/core/BytesCodec.java

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
11
package dev.zarr.zarrjava.core.codec.core;
22

33
import com.fasterxml.jackson.annotation.JsonValue;
4+
import dev.zarr.zarrjava.ZarrException;
45
import dev.zarr.zarrjava.core.codec.ArrayBytesCodec;
56
import ucar.ma2.*;
67

78
import java.nio.ByteBuffer;
89
import java.nio.ByteOrder;
910

1011
public abstract class BytesCodec extends ArrayBytesCodec {
11-
protected abstract ByteOrder getByteOrder();
12+
protected abstract ByteOrder getByteOrder() throws ZarrException;
1213

1314
@Override
14-
public Array decode(ByteBuffer chunkBytes) {
15-
chunkBytes.order(getByteOrder());
16-
DataType dtype = arrayMetadata.dataType.getMA2DataType();
17-
int[] shape = arrayMetadata.chunkShape;
18-
19-
// Array.factory does not support boolean arrays directly from ByteBuffer
20-
if (dtype == DataType.BOOLEAN) {
21-
int size = chunkBytes.remaining();
22-
boolean[] bools = new boolean[size];
23-
for (int i = 0; i < size; i++) {
24-
bools[i] = chunkBytes.get(i) != 0;
25-
}
15+
public Array decode(ByteBuffer chunkBytes) throws ZarrException {
16+
ByteOrder order = ByteOrder.BIG_ENDIAN; // Default for 1-byte types
17+
if (arrayMetadata.dataType.getByteCount() > 1)
18+
order = getByteOrder();
19+
chunkBytes.order(order);
20+
DataType dtype = arrayMetadata.dataType.getMA2DataType();
21+
int[] shape = arrayMetadata.chunkShape;
22+
23+
// Array.factory does not support boolean arrays directly from ByteBuffer
24+
if (dtype == DataType.BOOLEAN) {
25+
int size = chunkBytes.remaining();
26+
boolean[] bools = new boolean[size];
27+
for (int i = 0; i < size; i++) {
28+
bools[i] = chunkBytes.get(i) != 0;
29+
}
2630

27-
Index index = Index.factory(shape);
28-
return Array.factory(DataType.BOOLEAN, index, bools);
31+
Index index = Index.factory(shape);
32+
return Array.factory(DataType.BOOLEAN, index, bools);
33+
}
34+
return Array.factory(dtype, shape, chunkBytes);
2935
}
3036

31-
return Array.factory(dtype, shape, chunkBytes);
32-
}
3337
@Override
34-
public ByteBuffer encode(Array chunkArray) {
35-
ByteOrder order = getByteOrder();
38+
public ByteBuffer encode(Array chunkArray) throws ZarrException {
39+
ByteOrder order = ByteOrder.BIG_ENDIAN; // Default for 1-byte types
40+
if (arrayMetadata.dataType.getByteCount() > 1)
41+
order = getByteOrder();
3642

3743
// Boolean
3844
if (chunkArray instanceof ArrayBoolean) {
@@ -94,6 +100,10 @@ public ByteOrder getByteOrder() {
94100
throw new RuntimeException("Unreachable");
95101
}
96102
}
103+
104+
public static Endian nativeOrder() {
105+
return ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? LITTLE : BIG;
106+
}
97107
}
98108

99109
}

0 commit comments

Comments
 (0)