Skip to content

Commit dc35390

Browse files
committed
add support for attributes in Array and ArrayMetadata classes
1 parent e101af0 commit dc35390

File tree

11 files changed

+435
-14
lines changed

11 files changed

+435
-14
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public int ndim() {
4141

4242
public abstract Object parsedFillValue();
4343

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

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/v2/Array.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import dev.zarr.zarrjava.ZarrException;
5+
import dev.zarr.zarrjava.core.Attributes;
56
import dev.zarr.zarrjava.store.FilesystemStore;
67
import dev.zarr.zarrjava.store.StoreHandle;
78
import dev.zarr.zarrjava.utils.Utils;
@@ -46,13 +47,19 @@ protected Array(StoreHandle storeHandle, ArrayMetadata arrayMetadata) throws IOE
4647
* @throws ZarrException throws ZarrException if the Zarr array cannot be opened
4748
*/
4849
public static Array open(StoreHandle storeHandle) throws IOException, ZarrException {
50+
ObjectMapper mapper = makeObjectMapper();
51+
ArrayMetadata metadata = mapper.readValue(
52+
Utils.toArray(storeHandle.resolve(ZARRAY).readNonNull()),
53+
ArrayMetadata.class
54+
);
55+
if (storeHandle.resolve(ZATTRS).exists())
56+
metadata.attributes = mapper.readValue(
57+
Utils.toArray(storeHandle.resolve(ZATTRS).readNonNull()),
58+
Attributes.class
59+
);
4960
return new Array(
5061
storeHandle,
51-
makeObjectMapper()
52-
.readValue(
53-
Utils.toArray(storeHandle.resolve(ZARRAY).readNonNull()),
54-
ArrayMetadata.class
55-
)
62+
metadata
5663
);
5764
}
5865

@@ -143,6 +150,12 @@ public static Array create(StoreHandle storeHandle, ArrayMetadata arrayMetadata,
143150
}
144151
ObjectMapper objectMapper = makeObjectMapper();
145152
ByteBuffer metadataBytes = ByteBuffer.wrap(objectMapper.writeValueAsBytes(arrayMetadata));
153+
if (arrayMetadata.attributes != null) {
154+
StoreHandle attrsHandle = storeHandle.resolve(ZATTRS);
155+
ByteBuffer attrsBytes = ByteBuffer.wrap(
156+
objectMapper.writeValueAsBytes(arrayMetadata.attributes));
157+
attrsHandle.set(attrsBytes);
158+
}
146159
metadataHandle.set(metadataBytes);
147160
return new Array(storeHandle, arrayMetadata);
148161
}

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import com.fasterxml.jackson.annotation.JsonIgnore;
55
import com.fasterxml.jackson.annotation.JsonProperty;
66
import dev.zarr.zarrjava.ZarrException;
7+
import dev.zarr.zarrjava.core.Attributes;
78
import dev.zarr.zarrjava.core.chunkkeyencoding.ChunkKeyEncoding;
89
import dev.zarr.zarrjava.utils.MultiArrayUtils;
910
import dev.zarr.zarrjava.core.chunkkeyencoding.Separator;
1011
import dev.zarr.zarrjava.v2.chunkkeyencoding.V2ChunkKeyEncoding;
1112
import dev.zarr.zarrjava.v2.codec.Codec;
1213
import ucar.ma2.Array;
1314

15+
import javax.annotation.Nonnull;
1416
import javax.annotation.Nullable;
1517

1618

@@ -39,6 +41,10 @@ public class ArrayMetadata extends dev.zarr.zarrjava.core.ArrayMetadata {
3941
@Nullable
4042
public final Codec compressor;
4143

44+
@Nullable
45+
@JsonIgnore
46+
public Attributes attributes;
47+
4248
@JsonIgnore
4349
public CoreArrayMetadata coreArrayMetadata;
4450

@@ -54,6 +60,22 @@ public ArrayMetadata(
5460
@Nullable @JsonProperty(value = "filters", required = true) Codec[] filters,
5561
@Nullable @JsonProperty(value = "compressor", required = true) Codec compressor,
5662
@Nullable @JsonProperty(value = "dimension_separator") Separator dimensionSeparator
63+
) throws ZarrException {
64+
this(zarrFormat, shape, chunks, dataType, fillValue, order, filters, compressor, dimensionSeparator, null);
65+
}
66+
67+
68+
public ArrayMetadata(
69+
int zarrFormat,
70+
long[] shape,
71+
int[] chunks,
72+
DataType dataType,
73+
@Nullable Object fillValue,
74+
Order order,
75+
@Nullable Codec[] filters,
76+
@Nullable Codec compressor,
77+
@Nullable Separator dimensionSeparator,
78+
@Nullable Attributes attributes
5779
) throws ZarrException {
5880
super(shape, fillValue, dataType);
5981
if (zarrFormat != this.zarrFormat) {
@@ -78,6 +100,7 @@ public ArrayMetadata(
78100
}
79101
}
80102
this.compressor = compressor == null ? null : compressor.evolveFromCoreArrayMetadata(this.coreArrayMetadata);
103+
this.attributes = attributes;
81104
}
82105

83106

@@ -87,8 +110,6 @@ public int[] chunkShape() {
87110
return chunks;
88111
}
89112

90-
91-
92113
@Override
93114
public DataType dataType() {
94115
return dataType;
@@ -111,4 +132,14 @@ public ChunkKeyEncoding chunkKeyEncoding() {
111132
public Object parsedFillValue() {
112133
return parsedFillValue;
113134
}
135+
136+
@Override
137+
public @Nonnull Attributes attributes() throws ZarrException {
138+
if (attributes == null) {
139+
throw new ZarrException("Array attributes have not been set.");
140+
}
141+
return attributes;
142+
}
143+
144+
114145
}

0 commit comments

Comments
 (0)