org.eclipse.dawnsci.nexus.builder.AbstractNexusObjectProvider Maven / Gradle / Ivy
/*-
*******************************************************************************
* Copyright (c) 2015 Diamond Light Source Ltd.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthew Dickie - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.dawnsci.nexus.builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.dawnsci.nexus.NXdata;
import org.eclipse.dawnsci.nexus.NXobject;
import org.eclipse.dawnsci.nexus.NexusBaseClass;
import org.eclipse.dawnsci.nexus.builder.data.NexusDataBuilder;
import org.eclipse.dawnsci.nexus.builder.data.impl.PrimaryDataFieldModel;
import org.eclipse.january.dataset.ILazyWriteableDataset;
/**
* Abstract implementation of {@link NexusObjectProvider}.
*
*
Use this class to help implementing NexusObjectProvider, for example:
class Detector implements IRunnableDevice, INexusDevice {
private NexusObjectProvider prov;
public Detector() {
prov = new AbstractNexusObjectProvider(NexusBaseClass.NX_DETECTOR) {
protected NXdetector doCreateNexusObject(NexusNodeFactory nodeFactory) {
final NXdetectorImpl detector = nodeFactory.createNXdetector();
final int rank = 4;
detector.initializeLazyDataset(NXdetectorImpl.NX_DATA, rank, Dataset.FLOAT64);
return detector;
}
};
}
public NexusObjectProvider getNexusProvider() {
return prov;
}
public void write(...) {
// Use prov to help write the required datasets.
}
}
*
* @param nexus base class type, a subinterface of {@link NXobject}
*/
public abstract class AbstractNexusObjectProvider implements NexusObjectProvider {
public static final String DEFAULT_DATA_NODE_NAME = "data";
private final NexusBaseClass nexusBaseClass;
private N nexusObject = null;
private String name;
private String primaryDataFieldName = null;
private final LinkedHashSet axisDataFieldNames;
private final List additionalPrimaryDataFieldNames;
private Map primaryDataFieldModels = null;
private String defaultExternalFileName = null;
private Set externalFileNames = null;
private Map externalDatasetRanks = null;
private String defaultAxisDataFieldName = null;
private String collectionName = null;
private NexusBaseClass category = null;
private Boolean useDeviceNameInNXdata = null;
private Map properties = null;
/**
* Creates a new {@link AbstractNexusObjectProvider} for given name, base class type
* and data node name.
* @param name name
* @param nexusBaseClass base class type
*/
public AbstractNexusObjectProvider(String name, NexusBaseClass nexusBaseClass) {
this(name, nexusBaseClass, null);
}
/**
* Creates a new {@link AbstractNexusObjectProvider} for given name, base class type
* and data node name. The default data field will be used as the @signal
* for an {@link NexusBaseClass#NX_DETECTOR} when building an {@link NXdata} for this object,
* otherwise if this device is the default axis for a particular dimension of the
* @signal
field of the device, this is the field that will be added to the
* @axes
attribute of the {@link NXdata} group for that dimension.
*
* @param name name
* @param nexusBaseClass base class type
* @param defaultDataFieldName default data field, the default signal field for a detector,
* the default axis field for any other type of nexus object
* @param additionalDataFieldNames the names of any additional data fields
*/
public AbstractNexusObjectProvider(String name, NexusBaseClass nexusBaseClass,
String defaultDataFieldName, String... additionalDataFieldNames) {
this.name = name;
this.nexusBaseClass = nexusBaseClass;
this.axisDataFieldNames = new LinkedHashSet<>(additionalDataFieldNames.length);
if (nexusBaseClass == NexusBaseClass.NX_DETECTOR) {
setPrimaryDataFieldName(defaultDataFieldName);
} else {
setDefaultAxisDataFieldName(defaultDataFieldName);
}
if (additionalDataFieldNames.length > 0) {
this.axisDataFieldNames.addAll(Arrays.asList(additionalDataFieldNames));
}
// field names should be prefixed by the device name for axis devices (e.g. positioners)
setUseDeviceNameInNXdata(nexusBaseClass != NexusBaseClass.NX_DETECTOR);
this.additionalPrimaryDataFieldNames = new ArrayList<>();
}
public static String getDefaultName(NexusBaseClass nexusBaseClass) {
// the default name is the base class name without the initial 'NX' prefix,
// e.g. for 'NXpositioner' the default name is 'positioner'
return nexusBaseClass.toString().substring(2);
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getNexusObject()
*/
@Override
public final N getNexusObject() {
if (nexusObject == null) {
nexusObject = createNexusObject();
}
return nexusObject;
}
protected abstract N createNexusObject();
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getName()
*/
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getNexusBaseClass()
*/
@Override
public NexusBaseClass getNexusBaseClass() {
return nexusBaseClass;
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getExternalFileName()
*/
@Override
public Set getExternalFileNames() {
return externalFileNames == null ? Collections.emptySet() : externalFileNames;
}
/**
* Set the name of the external file that this device writes its data to.
* @param externalFileName external file name
*/
public void setDefaultExternalFileName(String externalFileName) {
defaultExternalFileName = externalFileName;
addExternalFileName(externalFileName);
}
/**
* Adds an external filename to this {@link AbstractNexusObjectProvider}.
* @param externalFileName
*/
public void addExternalFileName(String externalFileName) {
if (externalFileNames == null) {
externalFileNames = new HashSet<>(4);
}
externalFileNames.add(externalFileName);
}
/**
* A convenience method to add an external link to the given
* group node with the given name, while also setting the rank of the
* external dataset within this {@link AbstractNexusObjectProvider}.
* This is required to be set when adding a {@link NexusObjectProvider}
* with external links to a {@link NexusDataBuilder} in order for the
* axes
and <axisname>_indices
to be
* created.
*
* An external file must have been set by calling {@link #setDefaultExternalFileName(String)}
* prior to calling this method.
*
* @param groupNode group node to add external link to
* @param fieldName name of external dataset within the group
* @param pathToNode path of node to link to within the external file
* @param rank the rank of the
*/
public void addExternalLink(NXobject groupNode, String fieldName,
String pathToNode, int rank) {
if (defaultExternalFileName == null) {
throw new IllegalStateException("External file name not set.");
}
groupNode.addExternalLink(fieldName, defaultExternalFileName, pathToNode);
setExternalDatasetRank(fieldName, rank);
}
public void addExternalLink(NXobject groupNode, String fieldName, String externalFileName,
String pathToNode, int rank) {
addExternalFileName(externalFileName);
groupNode.addExternalLink(fieldName, externalFileName, pathToNode);
setExternalDatasetRank(fieldName, rank);
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getExternalDatasetRank(java.lang.String)
*/
@Override
public int getExternalDatasetRank(String fieldName) {
if (externalDatasetRanks == null || !externalDatasetRanks.containsKey(fieldName)) {
throw new IllegalArgumentException("No rank set for external dataset: " + fieldName);
}
return externalDatasetRanks.get(fieldName);
}
/**
* Set the rank of an external dataset within the nexus object returned by
* {@link #getNexusObject()}. The method {@link #setDefaultExternalFileName(String)} must
* have been invoked before calling this method.
* @param fieldName the name of the external dataset within the nexus object
* @param rank the rank of the external dataset
*/
public void setExternalDatasetRank(String fieldName, int rank) {
if (externalFileNames == null || externalFileNames.isEmpty()) {
throw new IllegalStateException("An external file name must be added before adding external datasets.");
}
if (externalDatasetRanks == null) {
externalDatasetRanks = new HashMap<>();
}
externalDatasetRanks.put(fieldName, rank);
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getDefaultDataFieldName()
*/
@Override
public String getPrimaryDataFieldName() {
if (primaryDataFieldName != null) {
return primaryDataFieldName;
}
// if the primary data field name hasn't been set, just use the first field
if (axisDataFieldNames != null && !axisDataFieldNames.isEmpty()) {
return axisDataFieldNames.iterator().next();
}
return null;
}
/**
* Sets the name of the field to use as the primary data field. If this device is the
* primary device for a scan then this field is used as the @signal
field
* for the {@link NXdata} group.
* @param primaryDataFieldName name of the primary data field
*/
public void setPrimaryDataFieldName(String primaryDataFieldName) {
this.primaryDataFieldName = primaryDataFieldName;
}
@Override
public String getDefaultAxisDataFieldName() {
return defaultAxisDataFieldName;
}
/**
* Sets the name of the data field for this device that acts as an axis.
* This field should only be set if this device is a scannable, i.e. a device that is
* set to a position at a particular point in the scan.
* @param defaultAxisDataFieldName
*/
public void setDefaultAxisDataFieldName(String defaultAxisDataFieldName) {
this.defaultAxisDataFieldName = defaultAxisDataFieldName;
if (defaultAxisDataFieldName != null) {
axisDataFieldNames.add(defaultAxisDataFieldName);
}
}
@Override
public List getAxisDataFieldNames() {
return new ArrayList(axisDataFieldNames);
}
/**
* Sets the names of the data fields for this device. Each data field will be added
* to any {@link NXdata} group created for this scan.
* @param axisDataFieldNames names of data fields
*/
public void setAxisDataFieldNames(String... axisDataFieldNames) {
this.axisDataFieldNames.clear();
this.axisDataFieldNames.addAll(Arrays.asList(axisDataFieldNames));
}
/**
* Adds the given name to the names of the axis data fields for this device. Each data field
* will be added to any {@link NXdata} group created for this scan (an {@link NXdata} group is
* created for each primary data field). In order to add a data field that should only
* be added to the {@link NXdata} group for a specific primary data field use
* {@link #addAxisDataFieldForPrimaryDataField(String, String, Integer, int...)}
* @param dataFieldName names of data fields
*/
public void addAxisDataFieldName(String dataFieldName) {
this.axisDataFieldNames.add(dataFieldName);
}
/**
* Adds the given names to the names of the data fields for this device. Each data field
* will be added to any {@link NXdata} group created for this scan (an {@link NXdata} group is
* created for each primary data field). In order to add a data field that should only
* be added to the {@link NXdata} group for a specific primary data field use
* {@link #addAxisDataFieldForPrimaryDataField(String, String, Integer, int...)}
* @param axisDataFieldNames names of data fields
*/
public void addAxisDataFieldNames(String... axisDataFieldNames) {
this.axisDataFieldNames.addAll(Arrays.asList(axisDataFieldNames));
}
/**
* Adds the given axis data field for the primary data field of this device. It will be
* added as an axis field to the {@link NXdata} group where the primary data field of this
* device is the @signal
field.
* @param dataFieldName name of data field
* @param defaultAxisDimension the dimension of the primary data field for which this
* field is a default axis
* @param dimensionMappings mappings between the dimensions of the axis data field and the
* primary data field for this device, can be omitted if the mapping is one-to-one as is
* usually the case
*/
public void addAxisDataField(String dataFieldName, Integer defaultAxisDimension, int... dimensionMappings) {
if (primaryDataFieldName == null) {
throw new IllegalStateException("Default writable data field not set.");
}
addAxisDataFieldForPrimaryDataField(dataFieldName, primaryDataFieldName,
defaultAxisDimension, dimensionMappings);
}
/**
* Returns the names of the data fields that are axes for the given primary data field
* within this device.
* @param primaryDataFieldName primary data field name
* @return names of data fields
*/
public List getAxisDataFieldsForPrimaryDataField(String primaryDataFieldName) {
PrimaryDataFieldModel primaryDataFieldModel = getPrimaryDataFieldModel(primaryDataFieldName, false);
if (primaryDataFieldModel == null) {
return Collections.emptyList();
}
return primaryDataFieldModel.getAxisFieldNames();
}
private PrimaryDataFieldModel getPrimaryDataFieldModel(String primaryDataFieldName, boolean create) {
if (primaryDataFieldModels == null) {
if (!create) return null;
primaryDataFieldModels = new HashMap<>();
}
PrimaryDataFieldModel primaryDataFieldModel = primaryDataFieldModels.get(primaryDataFieldName);
if (primaryDataFieldModel == null) {
if (!create) return null;
primaryDataFieldModel = new PrimaryDataFieldModel();
primaryDataFieldModels.put(primaryDataFieldName, primaryDataFieldModel);
}
return primaryDataFieldModel;
}
/**
* Adds a data field as an axis to a given primary data field. This field is only added
* to the {@link NXdata} group for the given primary data field (i.e. where it is the
* @signal
field).
* @param dataFieldName name of data field to add
* @param primaryDataFieldName name of primary data field that the new data field is an axis for
* @param defaultAxisDimension
* @param dimensionMappings (optional) dimension mappings between the new data field
* and the primary data. If this argument is not specified then the dimension mappings will
* be assumed to be {0, 1, 2, etc} if the data field is multidimensional, or
* { defaultAxisDimension } if the data field has a single dimension
*/
public void addAxisDataFieldForPrimaryDataField(String dataFieldName, String primaryDataFieldName,
Integer defaultAxisDimension, int... dimensionMappings) {
PrimaryDataFieldModel primaryDataFieldModel = getPrimaryDataFieldModel(primaryDataFieldName, true);
primaryDataFieldModel.addAxisField(dataFieldName, defaultAxisDimension, dimensionMappings);
}
/**
* Add an additional primary data field. This is a data field for which, when this
* device is the primary device in a scan, an additional {@link NXdata} group should be
* created with this field as the @signal
field.
* @param dataFieldName the name of the additional primary data field
*/
public void addAdditionalPrimaryDataFieldName(String dataFieldName) {
// TODO: is this the best name for this concept? it's a bit confusing
// alternatives: addPrimaryDataField, addSecondarySignalField etc
additionalPrimaryDataFieldNames.add(dataFieldName);
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getAdditionalPrimaryDataFieldNames()
*/
@Override
public List getAdditionalPrimaryDataFieldNames() {
return additionalPrimaryDataFieldNames;
}
/* (non-Javadoc)
* @see org.eclipse.dawnsci.nexus.builder.NexusObjectProvider#getCategory()
*/
@Override
public NexusBaseClass getCategory() {
return category;
}
public void setCategory(NexusBaseClass category) {
this.category = category;
}
public String getCollectionName() {
return collectionName;
}
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
@Override
public Integer getDefaultAxisDimension(String primaryDataFieldName, String axisDataFieldName) {
PrimaryDataFieldModel dataFieldModel = getPrimaryDataFieldModel(primaryDataFieldName, false);
if (dataFieldModel != null) {
return dataFieldModel.getDefaultAxisDimension(axisDataFieldName);
}
return null;
}
@Override
public int[] getDimensionMappings(String primaryDataFieldName, String axisDataFieldName) {
PrimaryDataFieldModel dataFieldModel = getPrimaryDataFieldModel(primaryDataFieldName, false);
if (dataFieldModel != null) {
return dataFieldModel.getDimensionMappings(axisDataFieldName);
}
return null;
}
public ILazyWriteableDataset getWriteableDataset(String fieldName) {
return getNexusObject().getLazyWritableDataset(fieldName);
}
public Boolean getUseDeviceNameInNXdata() {
return useDeviceNameInNXdata;
}
public void setUseDeviceNameInNXdata(boolean useDeviceNameInNXdata) {
this.useDeviceNameInNXdata = useDeviceNameInNXdata;
}
@Override
public Object getPropertyValue(String propertyName) {
if (properties == null) return null;
return properties.get(propertyName);
}
public void setPropertyValue(String propertyName, Object value) {
if (properties == null) {
properties = new HashMap<>(4);
}
properties.put(propertyName, value);
}
}