Skip to content

Commit

Permalink
[ADQL] Add prototype of new DataType
Browse files Browse the repository at this point in the history
  • Loading branch information
gmantele committed Jan 12, 2024
1 parent 23a989b commit e2e814a
Show file tree
Hide file tree
Showing 43 changed files with 1,683 additions and 0 deletions.
130 changes: 130 additions & 0 deletions ADQLLib/src/vollt/datatype/DataType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package vollt.datatype;

import java.util.Optional;

/**
* Representation of a data type.
*
* <h3>VOTable type system</h3>
* <p>
* Implementations can return the equivalent type in the VOTable type
* system. In other words: a {@link #getVotDatatype() datatype},
* an {@link #getVotArraysize() arraysize} and a
* {@link #getVotXtype() xtype}. Only the VOTable datatype is mandatory and
* must never be NULL.
* </p>
*
* <h3>Type comparison</h3>
* <p>
* In some cases, it is useful to merge two information. The condition to
* perform this operation is to ensure their data-types are compatible
* enough for a such purpose. That's the goal of the function
* {@link #canMergeWith(DataType)}.
* </p>
*
* <h3>Simple serialization</h3>
* <p>
* Any {@link DataType} can be serialized into a string thanks to
* {@link #toString()}. This latter should use the VOTable type
* information. Example: <code>char[*]</code>. See
* {@link SimpleType#toString()} for inspiration.
* </p>
*
* <h3>Implementing {@link DataType}</h3>
* <p>
* An implementation of the functions {@link #canMergeWith(DataType)}
* and {@link #toString()} is already provided in the abstract class
* {@link SimpleType}. It is recommended to extends this class when defining
* new {@link DataType} implementations.
* </p>
*
* @author Gr&eacute;gory Mantelet (CDS)
* @version 1.0 (05/2023)
*
* @see SimpleType
*/
public interface DataType {

boolean isNumeric();

boolean isBoolean();

boolean isBinary();

boolean isString();

boolean isGeometry();

boolean isTime();

boolean isArray();

/**
* Get the corresponding VOTable datatype.
*
* <p><b>IMPORTANT:</b>
* This function MUST never return NULL or an empty string.
* </p>
*
* @return The corresponding VOTable datatype.
*/
String getVotDatatype();

/**
* Get the size of the represented array in the VOTable representation.
*
* <p>
* This function must return an empty {@link Optional} object if
* this data type does not represent an array.
* </p>
*
* @return The corresponding VOTable array size.
*/
Optional<String> getVotArraysize();

/**
* Get the special type corresponding to this data type in the VOTable
* representation. In VOTable term, this piece of information is called an
* <code>XType</code>.
*
* <p>
* This function must return an empty {@link Optional} object if
* this data type does not represent a special VOTable type.
* </p>
*
* @return The corresponding VOTable XType.
*/
Optional<String> getVotXtype();

String toString();

/**
* Tell whether a data of this type could be merged with a data of the given
* type.
*
* <p>
* To return <code>true</code> this function has to evaluate whether
* the two given types are compatible at some level so that the two data
* to merge can be implicitly casted in each other type.
* </p>
*
* <p>
* By default (see {@link SimpleType#canMergeWith(DataType)}), two
* data types are declared compatible for merge when they are exactly
* the same class. But in some cases, this merge compatibility should be
* extended. For instance for exact numeric types: int, short and long.
* To indicate their compatibility, the corresponding {@link DataType}
* implementations should implement the same interface. For instance:
* {@link ExactNumericType} is the interface making {@link TypeShort},
* {@link TypeInteger} and {@link TypeLong} compatible for merge.
* </p>
*
* @param otherDatatype The data type of another datum.
*
* @return <code>true</code> if the two data types are compatible for merge,
* <code>false</code> otherwise (and especially if otherDatatype is
* <code>null</code>).
*/
boolean canMergeWith(final DataType otherDatatype);

}
9 changes: 9 additions & 0 deletions ADQLLib/src/vollt/datatype/DataTypeFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package vollt.datatype;

public interface DataTypeFactory {

DataType fromVOTable(final String datatype, final String arraysize, final String xtype);

static DataTypeFactory getDefault(){ return DataTypeFactoryImp.DEFAULT_INSTANCE; }

}
255 changes: 255 additions & 0 deletions ADQLLib/src/vollt/datatype/DataTypeFactoryImp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package vollt.datatype;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class DataTypeFactoryImp implements DataTypeFactory {

static final DataTypeFactoryImp DEFAULT_INSTANCE = new DataTypeFactoryImp();

/** Exhaustive collection of all supported scalar types. */
protected Map<String, Class<? extends ScalarType>> mapSupportedTypes = new HashMap<>();

public DataTypeFactoryImp() {
// Simple types:
support(TypeBit.class);
support(TypeBoolean.class);
support(TypeChar.class);
support(TypeDouble.class);
support(TypeFloat.class);
support(TypeInteger.class);
support(TypeLong.class);
support(TypeShort.class);
support(TypeUnicodeChar.class);
support(TypeUnsignedByte.class);

// Special types:
support(TypeCircleWithDouble.class);
support(TypeCircleWithFloat.class);
support(TypeIntervalOfDouble.class);
support(TypeIntervalOfFloat.class);
support(TypeIntervalOfInteger.class);
support(TypeIntervalOfLong.class);
support(TypeIntervalOfShort.class);
support(TypeMOC.class);
support(TypePointWithDouble.class);
support(TypePointWithFloat.class);
support(TypePolygonWithDouble.class);
support(TypePolygonWithFloat.class);
support(TypeRegion.class);
support(TypeShape.class);
support(TypeTimestamp.class);
}

/**
* Declare a data type to support.
*
* <p>
* This function should be used only by the constructor to update the
* collection of supported scalar types: {@link #mapSupportedTypes}.
* </p>
*
* <p><b>IMPORTANT:</b>
* The given class must represent a scalar type and must provide an empty
* constructor.
* </p>
*
* @param typeClass Class of the type to support.
*
* @throws IncorrectDataTypeClass If the given type cannot be instantiated
* or a similar type is already supported in
* this factory.
*/
protected void support(final Class<? extends ScalarType> typeClass) throws IncorrectDataTypeClass {
// Try to get an instance of the given type:
ScalarType typeInstance;
try {
final Constructor<? extends ScalarType> constructor = typeClass.getConstructor();
typeInstance = constructor.newInstance();
}catch(Exception ex) {
throw new IncorrectDataTypeClass("Cannot create a "+this.getClass().getSimpleName()+"! Cause: impossible to create an instance of "+typeClass.getName()+".", ex);
}

// Create a hash key with the XType and the datatype:
final String typeKey = buildTypeKey(typeInstance.getVotDatatype(), typeInstance.getVotXtype().orElse(null));
if (mapSupportedTypes.containsKey(typeKey))
throw new IncorrectDataTypeClass("Cannot create a "+this.getClass().getSimpleName()+"! Cause: a similar datatype (i.e. '" + typeKey + "') is already supported!");
else
mapSupportedTypes.put(typeKey, typeClass);
}

/**
* Build a key for mapping in a unique way a given data type.
*
* @param datatype A data type. SHOULD NOT BE <code>null</code>.
* @param xtype An extension of the given datatype. MAY BE
* <code>null</code>.
*
* @return The corresponding key.
*/
protected String buildTypeKey(final String datatype, final String xtype){
return (xtype == null ? "" : normalizeXtype(xtype)) + "#" + normalizeDatatype(datatype);
}

@Override
public DataType fromVOTable(final String datatype, final String arraysize, String xtype) {
// Search with the xtype and datatype:
ScalarType foundType = getType(datatype, xtype);

// If not found, search without the xtype, if any is provided:
if (foundType == null && xtype != null){
foundType = getType(datatype, null);
}

// If still not found, return an UnknownType:
if (foundType == null){
return new UnknownType(datatype, arraysize, xtype);
}
// If specified by arraysize, create a vector of this type:
else if (isVector(foundType, arraysize)){
return new VectorType(foundType, arraysize);
}
// Otherwise, return the found scalar type:
else{
return foundType;
}
}

/**
* Search for the exact match for the specified type and return an instance
* of this type.
*
* @param datatype VOTable datatype. SHOULD NOT BE <code>null</code>.
* @param xtype VOTable special type. MAY BE <code>null</code>.
*
* @return The corresponding scalar type,
* or <code>null</code> if no match is found.
*/
protected ScalarType getType(final String datatype, final String xtype){
// If no datatype, no type to return:
if (datatype == null || datatype.trim().isEmpty())
return null;

// Build the mapping key for the specified type:
String typeKey = buildTypeKey(datatype, xtype);

// Search for a corresponding type:
final Class<? extends ScalarType> foundType = mapSupportedTypes.get(typeKey);

// If found, create and return an instance:
if (foundType != null) {
try {
return foundType.newInstance();
} catch (Exception t) {
return null;
}
}else
return null;
}

/**
* Tell whether the given arraysize represents a vector of the given scalar
* type or not.
*
* <p>
* Some special types (e.g. timestamp, point) are actually arrays in the
* VOTable representation. In other words, for such type, the VOTable
* attribute "arraysize" is not empty. However, with a multi-dimensional
* arraysize, it is still possible to represent a vector of such special
* scalar type. This function aims to detect simple cases like vector of
* simple scalar types as well as complex cases like a vector of special
* types.
* </p>
*
* @param scalarType The scalar type that may be the vector subtype.
* @param arraysize The specified VOTable arraysize.
*
* @return <code>true</code> if the given arraysize represents a vector of
* the given scalar type,
* <code>false</code> if the final type is really just a scalar type.
*/
protected boolean isVector(final ScalarType scalarType, final String arraysize){
final int arrayDimension = (arraysize != null && !arraysize.trim().isEmpty()) ? arraysize.toLowerCase().split("x").length : 0;
if (arrayDimension > 0)
return (!scalarType.getVotArraysize().isPresent() || arrayDimension > 1);
else
return false;
}

/**
* Normalize the given VOTable datatype.
*
* <p>In practice, it means:</p>
* <ul>
* <li>leading and trailing space characters are removed,</li>
* <li>the whole string is lower-cased.</li>
* </ul>
*
* <p>
* <code>Null</code> is returned if the given datatype is
* <code>null</code> or an empty string.
* </p>
*
* @param datatype The datatype to normalize.
*
* @return The normalized datatype,
* or <code>null</code> if no datatype provided.
*/
protected String normalizeDatatype(String datatype){
// Nothing to do, if no xtype:
if (datatype == null)
return null;

// Remove leading and trailing spaces and put in lower case:
datatype = datatype.trim().toLowerCase();

// NULL if nothing to return:
if (datatype.isEmpty())
return null;
else
return datatype;
}

/**
* Normalize the given VOTable XType.
*
* <p>In practice, it means:</p>
* <ul>
* <li>the schema prefix is removed, if any is found
* (e.g. <code>adql:POINT</code>),</li>
* <li>leading and trailing space characters are removed,</li>
* <li>the whole string is lower-cased.</li>
* </ul>
*
* <p>
* <code>Null</code> is returned if the given XType is <code>null</code>
* or an empty string (even after schema prefix removal).
* </p>
*
* @param xtype The XType to normalize.
*
* @return The normalized XType,
* or <code>null</code> if no XType provided.
*/
protected String normalizeXtype(String xtype){
// Nothing to do, if no xtype:
if (xtype == null)
return null;

// Remove any schema prefix:
final int indSchemaPrefix = xtype.indexOf(':');
if (indSchemaPrefix >= 0)
xtype = xtype.substring(indSchemaPrefix+1);

// Remove leading and trailing spaces and put in lower case:
xtype = xtype.trim().toLowerCase();

// NULL if nothing to return:
if (xtype.isEmpty())
return null;
else
return xtype;
}

}
Loading

0 comments on commit e2e814a

Please sign in to comment.