Skip to content

Commit

Permalink
Add minimal service support for adding, removing and replacing single…
Browse files Browse the repository at this point in the history
…-cell data
  • Loading branch information
arteymix committed Feb 7, 2024
1 parent f1236d8 commit 9222a95
Show file tree
Hide file tree
Showing 17 changed files with 762 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import ubic.gemma.model.common.AbstractDescribable;

import java.io.Serializable;
import java.util.Objects;

public class QuantitationType extends AbstractDescribable implements Serializable {

Expand Down Expand Up @@ -214,6 +215,10 @@ public boolean equals( Object object ) {
}
final QuantitationType that = ( QuantitationType ) object;

if ( that.getId() != null && this.getId() != null ) {
return Objects.equals( that.getId(), this.getId() );
}

if ( that.getName() != null && this.getName() != null && !this.getName().equals( that.getName() ) ) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ public boolean equals( Object object ) {

@Override
public int hashCode() {
if ( getId() != null ) {
return Objects.hashCode( getId() );
}
return Objects.hash( super.hashCode(), Objects.hashCode( bioAssayDimension ) );
return Objects.hash( super.hashCode(), bioAssayDimension );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ public abstract class DataVector implements Identifiable, Serializable {
*/
@Override
public int hashCode() {
if ( id != null ) {
return Objects.hashCode( id );
}
return Objects.hash( expressionExperiment, quantitationType, Arrays.hashCode( data ) );
// also, we cannot hash the ID because it is assigned on creation
// hashing the data is wasteful because subclasses will have a design element to distinguish distinct vectors
return Objects.hash( expressionExperiment, quantitationType );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ public class DesignElementDataVector extends DataVector {

@Override
public int hashCode() {
if ( getId() != null ) {
return Objects.hash( getId() );
}
return Objects.hash( super.hashCode(), designElement );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

import lombok.Getter;
import lombok.Setter;
import org.springframework.util.Assert;
import ubic.gemma.core.util.ListUtils;
import ubic.gemma.model.common.Identifiable;
import ubic.gemma.model.expression.bioAssay.BioAssay;
import ubic.gemma.persistence.hibernate.CompressedStringListType;
import ubic.gemma.persistence.hibernate.IntArrayType;
import ubic.gemma.persistence.hibernate.ByteArrayType;

import javax.annotation.Nullable;
import javax.persistence.Transient;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;

import static java.util.Collections.unmodifiableList;
import static ubic.gemma.core.util.ListUtils.getSparseRangeArrayElement;

@Getter
Expand All @@ -30,7 +29,7 @@ public class SingleCellDimension implements Identifiable {
* <p>
* This is stored as a compressed, gzipped blob in the database. See {@link CompressedStringListType} for more details.
*/
private List<String> cellIds;
private List<String> cellIds = new ArrayList<>();

/**
* An internal collection for mapping cell IDs to their position in {@link #cellIds}.
Expand All @@ -44,42 +43,54 @@ public class SingleCellDimension implements Identifiable {
* <p>
* This should always be equal to the size of {@link #cellIds}.
*/
private Integer numberOfCells;
private int numberOfCells = 0;

/**
* Cell types, or null if unknown.
* Cell types assignment to individual cells from the {@link #cellTypeLabels} collections.
* <p>
* If supplied, its size must be equal to that of {@link #cellIds}.
*/
@Nullable
private int[] cellTypes;

/**
* Cell type labels, or null if unknown.
* <p>
* Those are user-supplied cell type identifiers. Its size must be equal to that of {@link #cellIds}.
* <p>
* This is stored as a compressed, gzipped blob in the database. See {@link CompressedStringListType} for more details.
*/
@Nullable
private List<String> cellTypes;
private List<String> cellTypeLabels;

/**
* Number of cell types.
* Number of distinct cell types.
* <p>
* This must always be equal to number of distinct elements of {@link #cellTypes}.
*/
@Nullable
private Integer numberOfCellTypes;
private Integer numberOfCellTypeLabels;

/**
* List of bioassays that each cell belongs to.
* <p>
* The {@link BioAssay} {@code bioAssays[i]} applies to all the cells in the interval {@code [bioAssaysOffset[i], bioAssaysOffset[i+1][}.
* To find the bioassay type of a given cell, use {@link #getBioAssay(int)}.
*/
private List<BioAssay> bioAssays;
private List<BioAssay> bioAssays = new ArrayList<>();

/**
* Offsets of the bioassays.
* <p>
* This always contain {@code bioAssays.size()} elements.
* <p>
* This is stored in the database using {@link IntArrayType}.
* This is stored in the database using {@link ByteArrayType}.
*/
private int[] bioAssaysOffset;
private int[] bioAssaysOffset = new int[0];

public List<String> getCellIds() {
return unmodifiableList( cellIds );
}

public void setCellIds( List<String> cellIds ) {
this.cellIds = cellIds;
Expand All @@ -98,14 +109,31 @@ public BioAssay getBioAssay( int index ) {
* Obtain the {@link BioAssay} for a given cell ID.
*/
public BioAssay getBioAssayByCellId( String cellId ) {
return getBioAssay( getCellIndex( cellId ) );
}

public String getCellTypeLabel( int index ) {
Assert.notNull( cellTypes, "No cell types have been assigned." );
Assert.notNull( cellTypeLabels, "No cell labels exist." );
return cellTypeLabels.get( cellTypes[index] );
}

/**
* Obtain a cell type label by cell ID.
*/
public String getCellTypeLabelByCellId( String cellId ) {
return getCellTypeLabel( getCellIndex( cellId ) );
}

private int getCellIndex( String cellId ) {
if ( cellIdToIndex == null ) {
cellIdToIndex = ListUtils.indexOfElements( cellIds );
}
Integer index = cellIdToIndex.get( cellId );
if ( index == null ) {
throw new IllegalArgumentException( "Cell ID not found: " + cellId );
}
return getBioAssay( index );
return index;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import lombok.Getter;
import lombok.Setter;
import ubic.gemma.persistence.hibernate.IntArrayType;
import ubic.gemma.persistence.hibernate.ByteArrayType;

import java.util.Arrays;
import java.util.Objects;

/**
* An expression data vector that contains data at the resolution of a single cell.
Expand All @@ -26,7 +29,21 @@ public class SingleCellExpressionDataVector extends DesignElementDataVector {
/**
* Positions of the non-zero data in the {@link #getData()} vector.
* <p>
* This is mapped in the database using {@link IntArrayType}.
* This is mapped in the database using {@link ByteArrayType}.
*/
private int[] dataIndices;

@Override
public boolean equals( Object object ) {
if ( this == object ) {
return true;
}
if ( !( object instanceof SingleCellExpressionDataVector ) ) {
return false;
}
SingleCellExpressionDataVector other = ( SingleCellExpressionDataVector ) object;
return super.equals( object )
&& Objects.equals( singleCellDimension, other.singleCellDimension )
&& Arrays.equals( dataIndices, other.dataIndices );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package ubic.gemma.persistence.hibernate;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.util.Assert;
import ubic.basecode.io.ByteArrayConverter;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.Properties;

/**
* Represents a vector of scalars stored as a byte array in a single column.
* <p>
* The following types are supported for the {@code arrayType} parameter:
* <ul>
* <li>{@code int}</li>
* <li>{@code double}</li>
* </ul>
* Other types supported by {@link ByteArrayConverter} can be added if necessary.
* @author poirigui
* @see ByteArrayConverter
*/
public class ByteArrayType implements UserType, ParameterizedType {

private enum ByteArrayTypes {
INT( int[].class ),
DOUBLE( double[].class );

private final Class<?> arrayClass;

ByteArrayTypes( Class<?> arrayClass ) {
this.arrayClass = arrayClass;
}
}

private final ByteArrayConverter converter = new ByteArrayConverter();
private final LobHandler lobHandler = new DefaultLobHandler();

private ByteArrayTypes arrayType;

@Override
public int[] sqlTypes() {
return new int[] { Types.BLOB };
}

@Override
public Class<?> returnedClass() {
return arrayType.arrayClass;
}

@Override
public boolean equals( Object x, Object y ) throws HibernateException {
switch ( arrayType ) {
case INT:
return Arrays.equals( ( int[] ) x, ( int[] ) y );
case DOUBLE:
return Arrays.equals( ( double[] ) x, ( double[] ) y );
default:
throw unsupportedArrayType( arrayType );
}
}

@Override
public int hashCode( Object x ) throws HibernateException {
switch ( arrayType ) {
case INT:
return Arrays.hashCode( ( int[] ) x );
case DOUBLE:
return Arrays.hashCode( ( double[] ) x );
default:
throw unsupportedArrayType( arrayType );
}
}

@Override
public Object nullSafeGet( ResultSet rs, String[] names, SessionImplementor session, Object owner ) throws HibernateException, SQLException {
byte[] data = lobHandler.getBlobAsBytes( rs, 0 );
if ( data != null ) {
switch ( arrayType ) {
case INT:
return converter.byteArrayToInts( data );
case DOUBLE:
return converter.byteArrayToDoubles( data );
default:
throw unsupportedArrayType( arrayType );
}
} else {
return null;
}
}

@Override
public void nullSafeSet( PreparedStatement st, Object value, int index, SessionImplementor session ) throws HibernateException, SQLException {
byte[] blob;
if ( value != null ) {
switch ( arrayType ) {
case INT:
blob = converter.intArrayToBytes( ( int[] ) value );
break;
case DOUBLE:
blob = converter.doubleArrayToBytes( ( double[] ) value );
break;
default:
throw unsupportedArrayType( arrayType );
}
} else {
blob = null;
}
lobHandler.getLobCreator().setBlobAsBytes( st, index, blob );
}

@Override
public Object deepCopy( Object value ) throws HibernateException {
if ( value == null ) {
return null;
}
switch ( arrayType ) {
case INT:
return ( ( int[] ) value ).clone();
case DOUBLE:
return ( ( double[] ) value ).clone();
default:
throw unsupportedArrayType( arrayType );
}
}

@Override
public boolean isMutable() {
return true;
}

@Override
public Serializable disassemble( Object value ) throws HibernateException {
return ( Serializable ) deepCopy( value );
}

@Override
public Object assemble( Serializable cached, Object owner ) throws HibernateException {
return deepCopy( cached );
}

@Override
public Object replace( Object original, Object target, Object owner ) throws HibernateException {
return deepCopy( original );
}

@Override
public void setParameterValues( Properties parameters ) {
Assert.isTrue( parameters != null && parameters.containsKey( "arrayType" ),
"There must be an 'arrayType' parameter in the type declaration." );
arrayType = ByteArrayTypes.valueOf( parameters.getProperty( "arrayType" ).toUpperCase() );
}

private HibernateException unsupportedArrayType( ByteArrayTypes type ) {
return new HibernateException( String.format( "Unsupported array type: %s.", type ) );
}
}
Loading

0 comments on commit 9222a95

Please sign in to comment.