Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/dev/zarr/zarrjava/core/ArrayMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public int ndim() {

public abstract Object parsedFillValue();

public @Nonnull abstract Attributes attributes() throws ZarrException;

public static Object parseFillValue(Object fillValue, @Nonnull DataType dataType)
throws ZarrException {
if (fillValue == null) {
Expand Down
241 changes: 241 additions & 0 deletions src/main/java/dev/zarr/zarrjava/core/Attributes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package dev.zarr.zarrjava.core;

import dev.zarr.zarrjava.ZarrException;

import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class Attributes extends HashMap<String, Object> {

public Attributes() {
super();
}

public Attributes (Function<Attributes, Attributes> attributeMapper) {
super();
attributeMapper.apply(this);
}


public Attributes(Map<String, Object> attributes) {
super(attributes);
}

public Attributes add(String s, Object o){
this.put(s, o);
return this;
}

public Attributes delete(String s){
this.remove(s);
return this;
}

public boolean getBoolean(String key) {
Object value = this.get(key);
if (value instanceof Boolean) {
return (Boolean) value;
}
throw new IllegalArgumentException("Value for key " + key + " is not a Boolean");
}

public int getInt(String key) {
Object value = this.get(key);
if (value instanceof Number) {
return ((Number) value).intValue();
}
throw new IllegalArgumentException("Value for key " + key + " is not an Integer");
}

public double getDouble(String key) {
Object value = this.get(key);
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
throw new IllegalArgumentException("Value for key " + key + " is not a Double");
}

public float getFloat(String key) {
Object value = this.get(key);
if (value instanceof Number) {
return ((Number) value).floatValue();
}
throw new IllegalArgumentException("Value for key " + key + " is not a Float");
}

@Nonnull
public String getString(String key) {
Object value = this.get(key);
if (value instanceof String) {
return (String) value;
}
throw new IllegalArgumentException("Value for key " + key + " is not a String");
}

@Nonnull
public List<Object> getList(String key) {
Object value = this.get(key);
if (value instanceof List) {
return (List<Object>) value;
}
throw new IllegalArgumentException("Value for key " + key + " is not a List");
}

public Attributes getAttributes(String key) {
Object value = this.get(key);
if (value instanceof Attributes) {
return (Attributes) value;
}
if (value instanceof Map) {
return new Attributes((Map<String, Object>) value);
}
throw new IllegalArgumentException("Value for key " + key + " is not an Attributes object");
}

public <T> T[] getArray(String key, Class<T> clazz) throws ZarrException {
Object value = this.get(key);
if (value instanceof Object[] && (((Object[]) value).length == 0 || clazz.isInstance(((Object[]) value)[0]) )) {
return (T[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
@SuppressWarnings("unchecked")
T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, list.size());
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (clazz.isInstance(elem)) {
array[i] = clazz.cast(elem);
} else {
// Try to find a constructor that takes the element's class
java.lang.reflect.Constructor<?> matched = null;
for (java.lang.reflect.Constructor<?> c : clazz.getConstructors()) {
Class<?>[] params = c.getParameterTypes();
if (params.length == 1 && params[0].isAssignableFrom(elem.getClass())) {
matched = c;
break;
}
}
if (matched != null) {
try {
array[i] = (T) matched.newInstance(elem);
} catch (Exception e) {
throw new ZarrException("Failed to convert element at index " + i + " to type " + clazz.getName(), e);
}
} else {
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());
}
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not a List or array of type " + clazz.getName());
}

public int[] getIntArray(String key) {
Object value = this.get(key);
if (value instanceof int[]) {
return (int[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
int[] array = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof Number) {
array[i] = ((Number) elem).intValue();
} else {
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not an int array or List");
}

public long[] getLongArray(String key) {
Object value = this.get(key);
if (value instanceof long[]) {
return (long[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
long[] array = new long[list.size()];
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof Number) {
array[i] = ((Number) elem).longValue();
} else {
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not a long array or List");
}

public double[] getDoubleArray(String key) {
Object value = this.get(key);
if (value instanceof double[]) {
return (double[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
double[] array = new double[list.size()];
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof Number) {
array[i] = ((Number) elem).doubleValue();
} else {
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not a double array or List");
}

public float[] getFloatArray(String key) {
Object value = this.get(key);
if (value instanceof float[]) {
return (float[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
float[] array = new float[list.size()];
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof Number) {
array[i] = ((Number) elem).floatValue();
} else {
throw new IllegalArgumentException("Element at index " + i + " is not a Number");
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not a float array or List");
}

public boolean[] getBooleanArray(String key) {
Object value = this.get(key);
if (value instanceof boolean[]) {
return (boolean[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
boolean[] array = new boolean[list.size()];
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof Boolean) {
array[i] = (Boolean) elem;
} else {
throw new IllegalArgumentException("Element at index " + i + " is not a Boolean");
}
}
return array;
}
throw new IllegalArgumentException("Value for key " + key + " is not a boolean array or List");
}
}
1 change: 1 addition & 0 deletions src/main/java/dev/zarr/zarrjava/core/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface Node {

String ZARR_JSON = "zarr.json";
String ZARRAY = ".zarray";
String ZATTRS = ".zattrs";
String ZGROUP = ".zgroup";

/**
Expand Down
78 changes: 73 additions & 5 deletions src/main/java/dev/zarr/zarrjava/v2/Array.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import dev.zarr.zarrjava.ZarrException;
import dev.zarr.zarrjava.core.Attributes;
import dev.zarr.zarrjava.store.FilesystemStore;
import dev.zarr.zarrjava.store.StoreHandle;
import dev.zarr.zarrjava.utils.Utils;
Expand Down Expand Up @@ -46,13 +47,19 @@ protected Array(StoreHandle storeHandle, ArrayMetadata arrayMetadata) throws IOE
* @throws ZarrException throws ZarrException if the Zarr array cannot be opened
*/
public static Array open(StoreHandle storeHandle) throws IOException, ZarrException {
ObjectMapper mapper = makeObjectMapper();
ArrayMetadata metadata = mapper.readValue(
Utils.toArray(storeHandle.resolve(ZARRAY).readNonNull()),
ArrayMetadata.class
);
if (storeHandle.resolve(ZATTRS).exists())
metadata.attributes = mapper.readValue(
Utils.toArray(storeHandle.resolve(ZATTRS).readNonNull()),
Attributes.class
);
return new Array(
storeHandle,
makeObjectMapper()
.readValue(
Utils.toArray(storeHandle.resolve(ZARRAY).readNonNull()),
ArrayMetadata.class
)
metadata
);
}

Expand Down Expand Up @@ -143,6 +150,12 @@ public static Array create(StoreHandle storeHandle, ArrayMetadata arrayMetadata,
}
ObjectMapper objectMapper = makeObjectMapper();
ByteBuffer metadataBytes = ByteBuffer.wrap(objectMapper.writeValueAsBytes(arrayMetadata));
if (arrayMetadata.attributes != null) {
StoreHandle attrsHandle = storeHandle.resolve(ZATTRS);
ByteBuffer attrsBytes = ByteBuffer.wrap(
objectMapper.writeValueAsBytes(arrayMetadata.attributes));
attrsHandle.set(attrsBytes);
}
metadataHandle.set(metadataBytes);
return new Array(storeHandle, arrayMetadata);
}
Expand All @@ -164,6 +177,61 @@ public static ArrayMetadataBuilder metadataBuilder(ArrayMetadata existingMetadat
return ArrayMetadataBuilder.fromArrayMetadata(existingMetadata);
}

private Array writeMetadata(ArrayMetadata newArrayMetadata) throws ZarrException, IOException {
return Array.create(storeHandle, newArrayMetadata, true);
}

/**
* Sets a new shape for the Zarr array. It only changes the metadata, no array data is modified or
* deleted. This method returns a new instance of the Zarr array class and the old instance
* becomes invalid.
*
* @param newShape the new shape of the Zarr array
* @throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array resize(long[] newShape) throws ZarrException, IOException {
if (newShape.length != metadata.ndim()) {
throw new IllegalArgumentException(
"'newShape' needs to have rank '" + metadata.ndim() + "'.");
}

ArrayMetadata newArrayMetadata = ArrayMetadataBuilder.fromArrayMetadata(metadata)
.withShape(newShape)
.build();
return writeMetadata(newArrayMetadata);
}

/**
* Sets the attributes of the Zarr array. It overwrites and removes any existing attributes. This
* method returns a new instance of the Zarr array class and the old instance becomes invalid.
*
* @param newAttributes the new attributes of the Zarr array
* @throws ZarrException throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array setAttributes(Attributes newAttributes) throws ZarrException, IOException {
ArrayMetadata newArrayMetadata =
ArrayMetadataBuilder.fromArrayMetadata(metadata, false)
.withAttributes(newAttributes)
.build();
return writeMetadata(newArrayMetadata);
}

/**
* Updates the attributes of the Zarr array. It provides a callback that gets the current
* attributes as input and needs to return the new set of attributes. The attributes in the
* callback may be mutated. This method overwrites and removes any existing attributes. This
* method returns a new instance of the Zarr array class and the old instance becomes invalid.
*
* @param attributeMapper the callback that is used to construct the new attributes
* @throws ZarrException throws ZarrException if the new metadata is invalid
* @throws IOException throws IOException if the new metadata cannot be serialized
*/
public Array updateAttributes(Function<Attributes, Attributes> attributeMapper) throws ZarrException, IOException {
return setAttributes(attributeMapper.apply(metadata.attributes));
}

@Override
public String toString() {
return String.format("<v2.Array {%s} (%s) %s>", storeHandle,
Expand Down
Loading
Loading